【算法学习-势能线段树】

势能线段树

势能均摊复杂度

  • 在N个数求gcd问题中,总时间复杂度为O(N+logC)
  • 那么势能均摊复杂度就为O(N+logC)/N = O(1+logC/N) = O(1)
  • 也就是对每个数来说gcd的操作的时间复杂度是O(1)的
  • 注意:可持久化数据结构由于其O(1)版本记录的特性,显然会使得操作被重现,当然,不止是使用可持久化数据结构时候要注意,任何可重置函数调用状态的操作在均摊算法中都是应该被警惕的。

例题:

区间开方,区间加减,区间查询

  • 与加减操作不同的是,区间开方不支持使用lazy。(因为区间内的数字不同,开方也不同)

  • 线段树中数字经过多次开平方就变成1(1e9的数字仅需要6次)

  • 所以如果区间内的数不同(max!=min)就暴力开方,直到区间所有数都相同,再借用lazy(这个是lazy是区间置数set)

  • 最坏情况需要暴力开方logN个节点,每次开方logN,m次操作,所以操作的时间复杂度就是\(O(Mlogn^2)\)

int a[N];·
struct SegTree{
    ll sum,lazy,set,max,min;
}tree[N<<2];

void down_lazy(int rt,int ch){//下传懒标记
    if(tree[rt].lazy){
        if(tree[ch].set){
            tree[ch].set+=tree[rt].lazy;
        }
        else{
            tree[ch].lazy+=tree[rt].lazy;
        }
    }
    if(tree[rt].set){
        tree[ch].lazy=0;
        tree[ch].set=tree[rt].set;
    }
}
void cal_lazy(int l,int r,int rt){ //计算当前节点
    if(tree[rt].lazy){
        tree[rt].sum+=tree[rt].lazy*(r-l+1);
        tree[rt].max+=tree[rt].lazy;
        tree[rt].min+=tree[rt].lazy;
    }
    if(tree[rt].set){
        tree[rt].sum = tree[rt].set*(r-l+1);
        tree[rt].max=tree[rt].set;
        tree[rt].min=tree[rt].set;
    }
}
void init_lazy(int rt){//lazy清除
    tree[rt].lazy = tree[rt].set = 0;
}
void push_down(int l,int r,int rt){
    if(!tree[rt].lazy&&!tree[rt].set) return ;
    int ch = rt<<1; int mid=l+r>>1;
    cal_lazy(l,r,rt);
    if(l!=r){
        down_lazy(rt,ch);
        down_lazy(rt,ch|1);
    }
    init_lazy(rt);
}
void push_up(int l,int r,int rt){
    int ch = rt<<1; int mid=l+r>>1;
    push_down(l,mid,rt<<1);
    push_down(mid+1,r,rt<<1|1);
    tree[rt].sum = tree[ch].sum + tree[ch|1].sum;
    tree[rt].max = max(tree[ch].max , tree[ch|1].max);
    tree[rt].min = min(tree[ch].min , tree[ch|1].min);
}
void tree_build(int l,int r,int rt){
    tree[rt].lazy=0;
    if(l==r){ tree[rt].max=tree[rt].min=tree[rt].sum=a[l]; return; }
    int mid =l+r>>1;
    tree_build(l,mid,rt<<1);tree_build(mid+1,r,rt<<1|1);
    push_up(l,r,rt);
}
void do_sqrt(int a,int b,int l,int r,int rt){
    if(a>r||b<l) return ;
    push_down(l,r,rt);
    int mid = l+r>>1;
    if(l>=a&&r<=b){
        if(tree[rt].min == tree[rt].max){//区间值相同时候才用懒标记
            tree[rt].set =sqrt(tree[rt].max+0.5);//只改懒标记
            return ;
        }

        do_sqrt(a,b,l,mid,rt<<1);
        do_sqrt(a,b,mid+1,r,rt<<1|1);
        push_up(l,r,rt);
        return;
    }

    do_sqrt(a,b,l,mid,rt<<1);
    do_sqrt(a,b,mid+1,r,rt<<1|1);
    push_up(l,r,rt);
}
ll get_sum(int a,int b,int l,int r,int rt){
    if(a>r||b<l) return 0;
    push_down(l,r,rt);
    if(l>=a&&r<=b){
        return tree[rt].sum;
    }
    int mid =l+r>>1;
    return  get_sum(a,b,l,mid,rt<<1)
            + get_sum(a,b,mid+1,r,rt<<1|1);
}
void modify(int a,int b,ll c,int l,int r,int rt ){
    if(a>r||b<l) return ;
    push_down(l,r,rt);
    if(l>=a&&r<=b){  //只改懒标记
        tree[rt].lazy+=c;
        return ;
    }
    int mid = l+r>>1;
    modify(a,b,c,l,mid,rt<<1);
    modify(a,b,c,mid+1,r,rt<<1|1);
    push_up(l,r,rt);
}
void de_bug(int l,int r,int rt){//debug-输出原数组的值
    if(l==r){ cout<<tree[rt].sum<<" ";return; }
    int mid =l+r>>1;
    push_down(l,r,rt);
    de_bug(l,mid,rt<<1);de_bug(mid+1,r,rt<<1|1);
}
int main(){
    int n,m,opt,l,r,x;
    cin>>n>>m;
    for(int i=1;i<=n;i++) cin>>a[i];

    tree_build(1,n,1);//建树
    while(m--){
        cin>>opt;
        if(opt==1){//开方
            cin>>l>>r;
            do_sqrt(l,r,1,n,1);
        }else if(opt==2){//加
            cin>>l>>r>>x;
            modify(l,r,x,1,n,1);
        }else if(opt==3){//查询
            cin>>l>>r;
            //de_bug(1,n,1);
            cout<<get_sum(l,r,1,n,1)<<endl;
        }
    }
    return 0;
}
posted @ 2021-09-28 10:43  qingyanng  阅读(107)  评论(1编辑  收藏  举报