先膜一下九老师 %%%
可以先感性理解一下,不断进行区间开根号操作,整个序列会不断“趋近于一致“。比如说:
3123 42 5 --> 55 6 2 --> 7 2 1 --> 2 1 1 --> 1 1 1
就算数字之间相差很大,搞几下就很接近了。
由于有区间加操作,所以可以想到从相邻数的差进行考虑。
区间加操作体现到差分数组上只是两个点的改变。
所以就有这样一种想法,不断的区间开根号使数与数之间的差减小,当一段数全部相等时开根号就变成了区间加了,而区间加是可以打标记的。如果不能打标记就直接递归下去。
但是这里容易忽略一种情况:9 8 -> 3 2
。
可以发现当 a=k2,b=a−1 时,开一次根号差值没变,还是1。所以这种情况也要转化为区间加来打标记。
现在我们得到了一个算法,直接呈现代码更直观:
void Sqrt(int p,int L,int R){ //开根号操作
if(Rreturn;
if(L<=seg[p].L&&seg[p].R<=R){
if(seg[p]._min==seg[p]._max||seg[p]._max-seg[p]._min==(LL)sqrt(seg[p]._max)-(LL)sqrt(seg[p]._min)){ //如果满足可以打标记的条件
LL tem=(LL)sqrt(seg[p]._max)-seg[p]._max;
seg[p].add_plus(tem); //打区间加的标记
return;
}
}
pushdown(p);
Sqrt(p<<1,L,R); Sqrt(p<<1|1,L,R);
maintain(p);
}
这样能通过全部数据,而且跑得很快。
这样的复杂度为什么科学呢?
设题目ai范围为V。考虑差分数字,两个V范围的数开个几次就差不多差值就没有了,而区间加操作体现到差分数组上只是两个点的改变,也就m次把差值增加V。
设计势能函数为所有相邻数差值和,一开始势能 为O(n∗V) ,所有区间加操作最多会使势能增加 O(m∗V) 。总势能最多 O((n+m)∗V) 。
现在有一个问题,V开几次根号就变成1呢?我们可以粗略估算一下,设V开k次根号后变为2:
V2−k=2
logV2=2−k
log2(logV2)=−k
log2(log2V)=k
一个点,给他开一次根号需要logn的复杂度(从根走到叶)。所以用 O(logn∗log(logV)) 的复杂度能使势能下降V。
总复杂度大概是 O((n+m)∗logn∗log(logV)) 。
下面是完整代码。
#include
#include
#include
using namespace std;
const int maxn=100005;
typedef long long LL;
inline int getint(){
char ch=getchar(); int res=0,ff=1;
while(!('0'<=ch&&ch<='9')){ if(ch=='-') ff=-1; ch=getchar(); }
while('0'<=ch&&ch<='9') res=res*10+ch-'0', ch=getchar();
return res*ff;
}
struct node{
int L,R; LL sum,_max,_min,tag;
void add_plus(LL val){ sum+=val*(R-L+1), _max+=val; _min+=val; tag+=val; }
} seg[maxn*4];
void pushdown(int p){
if(seg[p].tag){
seg[p<<1].add_plus(seg[p].tag); seg[p<<1|1].add_plus(seg[p].tag);
seg[p].tag=0;
}
}
void maintain(int p){
seg[p].sum=seg[p<<1].sum+seg[p<<1|1].sum;
seg[p]._min=min(seg[p<<1]._min,seg[p<<1|1]._min);
seg[p]._max=max(seg[p<<1]._max,seg[p<<1|1]._max);
}
void build(int p,int L,int R){
seg[p].L=L; seg[p].R=R;
if(L==R){ seg[p].sum=seg[p]._max=seg[p]._min=getint(); return; }
int mid=(L+R)>>1;
build(p<<1,L,mid); build(p<<1|1,mid+1,R);
maintain(p);
}
void Updata(int p,int L,int R,int val){
if(Rreturn;
if(L<=seg[p].L&&seg[p].R<=R){ seg[p].add_plus(val); return; }
pushdown(p);
Updata(p<<1,L,R,val); Updata(p<<1|1,L,R,val);
maintain(p);
}
void Sqrt(int p,int L,int R){
if(Rreturn;
if(L<=seg[p].L&&seg[p].R<=R){
if(seg[p]._min==seg[p]._max||seg[p]._max-seg[p]._min==(LL)sqrt(seg[p]._max)-(LL)sqrt(seg[p]._min)){
LL tem=(LL)sqrt(seg[p]._max)-seg[p]._max;
seg[p].add_plus(tem);
return;
}
}
pushdown(p);
Sqrt(p<<1,L,R); Sqrt(p<<1|1,L,R);
maintain(p);
}
LL Query(int p,int L,int R){
if(Rreturn 0;
if(L<=seg[p].L&&seg[p].R<=R) return seg[p].sum;
pushdown(p);
return Query(p<<1,L,R)+Query(p<<1|1,L,R);
}
int n,Q;
int main(){
freopen("uoj228.in","r",stdin);
freopen("uoj228.out","w",stdout);
scanf("%d%d",&n,&Q);
build(1,1,n);
while(Q--){
int pd=getint(),x=getint(),y=getint();
if(pd==1){ int z=getint(); Updata(1,x,y,z); } else
if(pd==2) Sqrt(1,x,y); else
if(pd==3) printf("%lld\n",Query(1,x,y));
}
return 0;
}