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

题意

[均摊 线段树] UOJ#228. 基础数据结构练习题_第1张图片

题解

先膜一下九老师 %%%
可以先感性理解一下,不断进行区间开根号操作,整个序列会不断“趋近于一致“。比如说:
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(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(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
#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;
} 

你可能感兴趣的:(线段树)