[均摊 线段树] UOJ#228. 基础数据结构练习题

来源:互联网 发布:java web 高并发 编辑:程序博客网 时间:2024/06/05 09:19

题意

这里写图片描述

题解

先膜一下九老师 %%%
可以先感性理解一下,不断进行区间开根号操作,整个序列会不断“趋近于一致“。比如说:
3123 42 5 --> 55 6 2 --> 7 2 1 --> 2 1 1 --> 1 1 1
就算数字之间相差很大,搞几下就很接近了。
由于有区间加操作,所以可以想到从相邻数的差进行考虑。
区间加操作体现到差分数组上只是两个点的改变。
所以就有这样一种想法,不断的区间开根号使数与数之间的差减小,当一段数全部相等时开根号就变成了区间加了,而区间加是可以打标记的。如果不能打标记就直接递归下去。
但是这里容易忽略一种情况:9 8 -> 3 2
可以发现当a=k2,b=a1时,开一次根号差值没变,还是1。所以这种情况也要转化为区间加来打标记。
现在我们得到了一个算法,直接呈现代码更直观:

void Sqrt(int p,int L,int R){ //开根号操作    if(R<seg[p].L||seg[p].R<L) return;    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(nV),所有区间加操作最多会使势能增加O(mV)。总势能最多O((n+m)V)
现在有一个问题,V开几次根号就变成1呢?我们可以粗略估算一下,设V开k次根号后变为2:
V2k=2
logV2=2k
log2(logV2)=k
log2(log2V)=k
一个点,给他开一次根号需要logn的复杂度(从根走到叶)。所以用O(lognlog(logV)) 的复杂度能使势能下降V。
总复杂度大概是O((n+m)lognlog(logV))

下面是完整代码。

#include<cstdio>#include<cmath>#include<algorithm>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(R<seg[p].L||seg[p].R<L) return;    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(R<seg[p].L||seg[p].R<L) return;    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(R<seg[p].L||seg[p].R<L) return 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;} 
原创粉丝点击