UOJ #228 - 基础数据结构练习题(势能线段树+复杂度分析)
神仙题。
乍一看和经典题 花神游历各国有一点像,只不过多了一个区间加操作。不过多了这个区间加操作就无法再像花神游历各国那样暴力开根直到最小值为 \(1\) 为止的做法了,稍微感性理解一下即可明白,比方说你对一个已经全变为 \(1\) 的长度为 \(10^5\) 区间再加上 \(10^5\),那还需要 \(10^5\times\log 10^5\) 次区间修改操作才能变回原样,这样一来复杂度必然爆炸。
注意到我们对一个区间进行全局加,是不影响它的极差的,因此我们考虑用极差的角度思考这个问题,对于一段区间我们记录它的最大值和最小值,如果它们相等,即极差为 \(0\),那么显然区间开根就可以归约到区间加,直接打个标记即可。否则我们就继续递归左右区间进行修改。
为什么这样复杂度就对了呢?我们考虑记一个区间的容为这个区间中元素的极差,不难发现我们对一个区间进行开根后容也会开根,因为假设区间最大值为 \(a\),最小值为 \(b\),那么本来容为 \(a-b\),开根后容为 \(\sqrt{a}-\sqrt{b}\),而 \(a-b=(\sqrt{a}-\sqrt{b})(\sqrt{a}+\sqrt{b})\),故 \(\dfrac{a-b}{\sqrt{a}-\sqrt{b}}>\sqrt{a}-\sqrt{b}\),证毕。也就是说我们对一个区间最多进行 \(\log n\) 次操作即可让它的容变为 \(0\)。
接下来考虑修改操作,由于我们要修改的区间要拆分成 \(\log n\) 个区间,而如果我们对一个区间进行全局加,是不影响它的极差的,因此一次修改最多影响 \(\log n\) 个区间的容,而就算我们将这 \(\log n\) 的区间的容都变得很大,打个比方,都变成 \(10^{10}\),那最多还是只需要 \(\log 10^{10}\times\log n\) 次操作才能将这些区间的容又变回 \(0\),因此每次区间带来的操作次数的影响是 \(\log n\log A\) 的,其中 \(A\) 为值域,因此总复杂度上界为 \(n\log A+m\log n\log A\),且异常跑不满。
但由于下取整可能会带来的误差,直接这样写是无法通过的,比方说序列 \(3\ 4\ 3\ 4\),开根后得到 \(1\ 2\ 1\ 2\),极差不变。因此我们可以构造出这样的 hack 数据:
100000 100000
255 256 255 256 ... 255 256
2 1 100000
2 1 100000
2 1 100000
3 1 100000
1 1 100000 255
2 1 100000
2 1 100000
2 1 100000
3 1 100000
1 1 100000 255
...
2 1 100000
2 1 100000
2 1 100000
3 1 100000
1 1 100000 255
出现这样的情况就 GG 了。
不过不难发现这种情况会发生当且仅当整段序列极差为 \(1\),并且开根后极差仍为 \(1\),这种情况还是可以规约为区间减的形式,稍微特判一下即可。
u1s1 这种题就是说起来好像很简单的亚子,但思考过程还是挺值得玩味的
const int MAXN=1e5;
int n,qu,a[MAXN+5];
struct node{int l,r;ll mx,mn,sum,lz;} s[MAXN*4+5];
void pushup(int k){
s[k].mn=min(s[k<<1].mn,s[k<<1|1].mn);
s[k].mx=max(s[k<<1].mx,s[k<<1|1].mx);
s[k].sum=s[k<<1].sum+s[k<<1|1].sum;
}
void build(int k,int l,int r){
s[k].l=l;s[k].r=r;if(l==r){s[k].mn=s[k].mx=s[k].sum=a[l];return;}
int mid=l+r>>1;build(k<<1,l,mid);build(k<<1|1,mid+1,r);pushup(k);
}
void pushdown(int k){
if(s[k].lz){
s[k<<1].mn+=s[k].lz;s[k<<1].mx+=s[k].lz;s[k<<1].lz+=s[k].lz;
s[k<<1].sum+=s[k].lz*(s[k<<1].r-s[k<<1].l+1);
s[k<<1|1].mn+=s[k].lz;s[k<<1|1].mx+=s[k].lz;s[k<<1|1].lz+=s[k].lz;
s[k<<1|1].sum+=s[k].lz*(s[k<<1|1].r-s[k<<1|1].l+1);
s[k].lz=0;
}
}
void modify(int k,int l,int r,int x){
if(l<=s[k].l&&s[k].r<=r) return s[k].mn+=x,s[k].mx+=x,s[k].sum+=1ll*(r-l+1)*x,s[k].lz+=x,void();
pushdown(k);int mid=s[k].l+s[k].r>>1;
if(r<=mid) modify(k<<1,l,r,x);
else if(l>mid) modify(k<<1|1,l,r,x);
else modify(k<<1,l,mid,x),modify(k<<1|1,mid+1,r,x);
pushup(k);
}
ll query(int k,int l,int r){
if(l<=s[k].l&&s[k].r<=r) return s[k].sum;
pushdown(k);int mid=s[k].l+s[k].r>>1;
if(r<=mid) return query(k<<1,l,r);
else if(l>mid) return query(k<<1|1,l,r);
else return query(k<<1,l,mid)+query(k<<1|1,mid+1,r);
}
void sqrt(int k,int l,int r){
if(l<=s[k].l&&s[k].r<=r){
ll mnv=(ll)sqrt(s[k].mn),mxv=(ll)sqrt(s[k].mx);
if(s[k].mn==s[k].mx){
ll dif=mnv-s[k].mn;
s[k].mn+=dif;s[k].mx+=dif;s[k].sum+=dif*(r-l+1);s[k].lz+=dif;
} else if(s[k].mx-s[k].mn==1&&mxv-mnv==1){
ll dif=mnv-s[k].mn;
s[k].mn+=dif;s[k].mx+=dif;s[k].sum+=dif*(r-l+1);s[k].lz+=dif;
} else {
int mid=l+r>>1;pushdown(k);
sqrt(k<<1,l,mid);sqrt(k<<1|1,mid+1,r);
pushup(k);
} return;
} pushdown(k);int mid=s[k].l+s[k].r>>1;
if(r<=mid) sqrt(k<<1,l,r);
else if(l>mid) sqrt(k<<1|1,l,r);
else sqrt(k<<1,l,mid),sqrt(k<<1|1,mid+1,r);
pushup(k);
}
int main(){
scanf("%d%d",&n,&qu);
for(int i=1;i<=n;i++) scanf("%d",&a[i]);
build(1,1,n);
while(qu--){
int opt,l,r,x;scanf("%d",&opt);
switch(opt){
case 1:{scanf("%d%d%d",&l,&r,&x);modify(1,l,r,x);break;}
case 2:{scanf("%d%d",&l,&r);sqrt(1,l,r);break;}
case 3:{scanf("%d%d",&l,&r);printf("%lld\n",query(1,l,r));break;}
}
}
return 0;
}
UOJ #228 - 基础数据结构练习题(势能线段树+复杂度分析)的更多相关文章
- uoj#228. 基础数据结构练习题(线段树)
传送门 只有区间加区间开方我都会--然而加在一起我就gg了-- 然后这题的做法就是对于区间加直接打标记,对于区间开方,如果这个区间的最大值等于最小值就直接区间覆盖(据ljh_2000大佬说这个区间覆盖 ...
- 【线段树】uoj#228. 基础数据结构练习题
get到了标记永久化 sylvia 是一个热爱学习的女孩子,今天她想要学习数据结构技巧. 在看了一些博客学了一些姿势后,她想要找一些数据结构题来练练手.于是她的好朋友九条可怜酱给她出了一道题. 给出一 ...
- 【UOJ228】基础数据结构练习题(线段树)
[UOJ228]基础数据结构练习题(线段树) 题面 UOJ 题解 我们来看看怎么开根? 如果区间所有值都相等怎么办? 显然可以直接开根 如果\(max-sqrt(max)=min-sqrt(min)\ ...
- uoj #228. 基础数据结构练习题 线段树
#228. 基础数据结构练习题 统计 描述 提交 自定义测试 sylvia 是一个热爱学习的女孩子,今天她想要学习数据结构技巧. 在看了一些博客学了一些姿势后,她想要找一些数据结构题来练练手.于是她的 ...
- uoj#228 基础数据结构练习题
题面:http://uoj.ac/problem/228 正解:线段树. 我们可以发现,开根号时一个区间中的数总是趋近相等.判断一个区间的数是否相等,只要判断最大值和最小值是否相等就行了.如果这个区间 ...
- uoj#228. 基础数据结构练习题(线段树区间开方)
题目链接:http://uoj.ac/problem/228 代码:(先开个坑在这个地方) #include<bits/stdc++.h> using namespace std; ; l ...
- UOJ #228. 基础数据结构练习题 线段树 + 均摊分析 + 神题
题目链接 一个数被开方 #include<bits/stdc++.h> #define setIO(s) freopen(s".in","r",st ...
- HDU6315 Naive Operations(线段树 复杂度分析)
题意 题目链接 Sol 这题关键是注意到题目中的\(b\)是个排列 那么最终的答案最多是\(nlogn\)(调和级数) 设\(d_i\)表示\(i\)号节点还需要加\(d_i\)次才能产生\(1\)的 ...
- uoj#119. 【UR #8】决战圆锥曲线(线段树+复杂度分析)
题解 传送门 题解 然而要我来说我感觉只是个爆搜啊-- //minamoto #include<bits/stdc++.h> #define R register #define ll l ...
随机推荐
- 【c++ Prime 学习笔记】第4章 表达式
表达式由一个或多个运算对象组成,对表达式求值返回结果. 字面值和变量是最简单的表达式 把运算符和运算对象组合可得到复杂表达式. 4.1 基础 4.1.1 基本概念 一元运算符作用于一个对象,如取地址符 ...
- Java:反射小记
Java:反射小记 对 Java 中的 反射,做一个微不足道的小小小小记 概念 Java 反射指的是在 Java 程序运行状态中,对于任何一个类,都可以获得这个类的所有属性和方法:对于给定的一个对象, ...
- allegro查看线宽的方法
- numpy数组的计算
1.数组的形状 查看数组的形状: import numpy as np a = np.array([[1, 2, 3, 4, 5], [5, 6, 7, 8, 9]]) print(a.shape) ...
- Python大数据应用
一.三国演义人物出场统计 先检查安装包 1.jieba库基本介绍 (1)jieba库概述 jieba是优秀的中文分词第三方库 中文文本需要通过分词获得单个的词语 jieba是优秀的中文分词第三方库,需 ...
- MyBatis源码分析(三):MyBatis初始化(配置文件读取和解析)
一. 介绍MyBatis初始化过程 项目是简单的Mybatis应用,编写SQL Mapper,还有编写的SqlSessionFactoryUtil里面用了Mybatis的IO包里面的Resources ...
- 最新JS正则表达式验证手机号码(2019)
根据移动.联通.电信的电话号码号段,实现一个简单的正则表达式来验证手机号码: // 手机号校验 export function isPhoneNumber(phoneNum) { // let reg ...
- poj 3041 Asteroids(最小点覆盖)
题意: N*N的矩阵,有K个敌人,坐标分别是(C1,C1),.....,(Rk,Ck). 有一个武器,每发射一次,可消掉某行或某列上的所有的敌人. 问消灭所有敌人最少需要多少发. 思路: 二分建图:左 ...
- Docker 18.03 Centos7.6 安装 内网
首先访问https://download.docker.com/linux/centos/7/x86_64/stable/Packages/获取对应版本的rpm包docker包docker-ce-18 ...
- Get value from agent failed: cannot connect to [[192.168.8.102]:10050]: [113] No route to host
192.168.8.102主机down掉,开机就可以