zoukankan      html  css  js  c++  java
  • 【洛谷5280】[ZJOI2019] 线段树(线段树大力分类讨论)

    点此看题面

    大致题意: 给你一棵线段树,两种操作。一种操作将每棵线段树复制成两个,然后在这两个线段树中的一个上面进行(Modify(l,r))。另一种操作询问所有线段树的(tag)总和。

    大力分类讨论

    我们考虑用线段树来进行维护。

    定义一个(f_{rt}),表示在当前所有线段树中编号为(rt)的节点上的(tag)之和

    然后对于每次修改,就需要大力分类讨论,来计算新增加的(2^{t-1})棵树对(f_x)的贡献

    • 当这个节点未被访问到

      显然,就相当于此次操作对该节点没有任何影响,也就是新产生的(2^{t-1})棵树中(tag)不会发生任何变化,因此直接将(f_{rt}*=2)即可。

    • 当这个节点是被修改的节点

      如果这个节点是被修改的节点,则显然新产生的(2^{t-1})棵树中这个点会被赋值为(1),因此将(f_{rt}+=2^{t-1})即可。

    • 当这个节点被访问到且进行过(PushDown)操作

      由于进行过了(PushDown),则显然新产生的(2^{t-1})棵树中这个点会被赋值为(0),因此(f_{rt})不会发生任何变化。

    • 当这个节点被访问到但未进行任何操作

      如果这个节点被访问到但未进行任何操作,那么这个点的(tag)值就取决于这个节点到根节点的路径上(包括这个节点)是否存在至少一个节点(tag)值为(1),因为这样就能经过若干次(PushDown)影响到这一位上了。

      而这也是最复杂的一种情况,下面会单独对这种情况进行进一步的讨论。

    处理被访问到但未进行任何操作的节点

    考虑再记录一个(g_{rt}),表示当前所有线段树中编号为(rt)的节点到根节点的路径上(包括该节点)存在至少一个节点(tag)值为(1)的方案数

    则这样一来在这种情况下我们就可以直接将(f_{rt}+=g_{rt})了。

    但就有了一个新的问题,如何维护(g_{rt})

    于是又要进行一波与先前类似的分类讨论。

    • 当这个节点未被访问到

      同样,就相当于此次操作对该节点没有任何影响,因此直接将(g_{rt}*=2)即可。

    • 当这个节点是被修改的节点

      如果这个节点是被修改的节点,则显然新产生的(2^{t-1})棵树中这个点会被赋值为(1),因此新产生的(2^{t-1})棵树中这个点到根节点的路径上必然存在(tag)值为(1)的点(即这个点本身),因此将(g_{rt}+=2^{t-1})即可。

    • 当这个节点被访问到且进行过(PushDown)操作

      由于进行过了(PushDown),则显然这个点到根的路径上的所有节点全都(PushDown)过,因此新产生的(2^{t-1})棵树中这个点到根节点的路径上不存在(tag)值为(1)的点,因此(g_{rt})不会发生任何变化。

    • 当这个节点被访问到但未进行任何操作

      (g_{rt}*=2)即可。

    整理+优化

    接下来,我们来整理一下上面的内容:

    情况 (f_{rt}) (g_{rt})
    当这个节点未被访问到 (f_{rt}*=2) (g_{rt}*=2)
    当这个节点是被修改的节点 (f_{rt}+=2^{t-1}) (g_{rt}+=2^{t-1})
    当这个节点被访问到且进行过(PushDown)操作 无变化 无变化
    当这个节点被访问到但未进行任何操作 (f_{rt}+=g_{rt}) (g_{rt}*=2)

    考虑到对于未访问到的节点,我们一律都是将答案乘(2)的。

    那么,我们能不能换一种思维,即每次不修改未被访问到的节点,而是把其余三种情况时的(f_{rt})(g_{rt})除以(2),然后在输出答案时把答案乘上(2^t)

    则就得到了这样一个新表格:

    情况 (f_{rt}) (g_{rt})
    当这个节点未被访问到 无变化 无变化
    当这个节点是被修改的节点 (f_{rt}=frac{f_{rt}+1}2) (g_{rt}=frac{g_{rt}+1}2)
    当这个节点被访问到且进行过(PushDown)操作 无变化 无变化
    当这个节点被访问到但未进行任何操作 (f_{rt}=frac{f_{rt}+g_{rt}}2) 无变化

    这样就方便许多。

    维护(g)的修改

    但是,我们还是要注意,要在(PushDown)的同时维护(g)的修改。

    我们用(tag_{rt})来记录当前节点的(g)在上一次(PushDown)后被修改过多少次

    则我们需要将(rt<<1)(rt<<1|1)(g)分别进行(tag_{rt})(g=frac{g+1}2)

    设要进行(tag_{rt})(g=frac{g+1}2)的点为(k),易得最终结果为:

    [frac {g_k+2^{tag_{rt}-1}+2^{tag_{rt}-2}+...+2^{tag_{rt}}}{2^{tag_{rt}}} ]

    式子的后半部分显然可以用等底数列求和公式化简,得到:

    [frac {g_k+{2^{tag_{rt}}-1}}{2^{tag_{rt}}}=frac {g_k}{2^{tag_{rt}}}+1-frac 1{2^{tag_{rt}}} ]

    分母可以预处理(2)的幂的逆元,然后就差不多了。

    代码

    #include<bits/stdc++.h>
    #define Tp template<typename Ty>
    #define Ts template<typename Ty,typename... Ar>
    #define Reg register
    #define RI Reg int
    #define Con const
    #define CI Con int&
    #define I inline
    #define W while
    #define N 100000
    #define X 998244353
    #define Inc(x,y) ((x+=(y))>=X&&(x-=X))
    #define Dec(x,y) ((x-=(y))<0&&(x+=X))
    #define Shl(x) ((x<<=1)>=X&&(x-=X))
    using namespace std;
    int n;
    class FastIO
    {
        private:
            #define FS 100000
            #define tc() (A==B&&(B=(A=FI)+fread(FI,1,FS,stdin),A==B)?EOF:*A++)
            #define pc(c) (C^FS?FO[C++]=c:(fwrite(FO,1,C,stdout),FO[(C=0)++]=c))
            #define tn (x<<3)+(x<<1)
            #define D isdigit(c=tc())
            int T,C;char c,*A,*B,FI[FS],FO[FS],S[FS];
        public:
            I FastIO() {A=B=FI;}
            Tp I void read(Ty& x) {x=0;W(!D);W(x=tn+(c&15),D);}
            Tp I void write(Ty x) {W(S[++T]=x%10+48,x/=10);W(T) pc(S[T--]);}
            Ts I void read(Ty& x,Ar&... y) {read(x),read(y...);}
            Tp I void writeln(Con Ty& x) {write(x),pc('
    ');}
            I void clear() {fwrite(FO,1,C,stdout),C=0;}
    }F;
    class SegmentTree//线段树
    {
        private:
            #define PD(x) O[x].t&&//下传标记
            (
                O[x<<1].g=((1LL*O[x<<1].g*I2[O[x].t]%X+1-I2[O[x].t])%X+X)%X,//更新左儿子g值
                O[x<<1|1].g=((1LL*O[x<<1|1].g*I2[O[x].t]%X+1-I2[O[x].t])%X+X)%X,//更新右儿子g值
                O[x<<1].t+=O[x].t,O[x<<1|1].t+=O[x].t,O[x].t=0//下传修改次数
            )
            int ans,pw,I2[N+5];struct node {int f,g,t;}O[N<<2];
            I void upt(CI l,CI r,CI rt,CI tl,CI tr)//修改
            {
                Dec(ans,O[rt].f),O[rt].f=1LL*O[rt].f*I2[1]%X,O[rt].g=1LL*O[rt].g*I2[1]%X;//先删除原来贡献,并将当前f,g除以2
                if(tl<=l&&r<=tr) Inc(O[rt].f,I2[1]),Inc(O[rt].g,I2[1]);//分类讨论
                else if(tr<l||r<tl)	Inc(O[rt].f,O[rt].g),Shl(O[rt].g);
                if(Inc(ans,O[rt].f),tr<l||r<tl) return;if(tl<=l&&r<=tr) return (void)(++O[rt].t);//加回新答案,然后模拟线段树过程
                PD(rt);RI mid=l+r>>1;upt(l,mid,rt<<1,tl,tr),upt(mid+1,r,rt<<1|1,tl,tr);
            }
        public:
            I SegmentTree() {pw=I2[0]=1,I2[1]=X+1>>1;}
            I void Init(CI x) {for(RI i=2;i<=x;++i) I2[i]=1LL*I2[i-1]*I2[1]%X;}//预处理2的幂的逆元
            I void Update(CI x,CI y) {upt(1,n,1,x,y),Shl(pw);}
            I int GetAns() {return 1LL*ans*pw%X;}//最后答案乘上2^t
    }S;
    int main()
    {
        RI Qtot,op,x,y;F.read(n,Qtot),S.Init(Qtot);
        W(Qtot--) F.read(op),op^2?(F.read(x,y),S.Update(x,y)):F.writeln(S.GetAns());
        return F.clear(),0;
    }
    
  • 相关阅读:
    创建索引
    异常处理之ThreadException、unhandledException及多线程异常处理
    Extjs ComboBox常用的配置
    制作Visual Studio项目模板
    wince更改桌面
    创建链接服务器
    IP查询接口
    软件工程未来发展趋势
    在SQL Server数据库开发中的十大问题
    .NET 2.0中的企业库异常处理块
  • 原文地址:https://www.cnblogs.com/chenxiaoran666/p/Luogu5280.html
Copyright © 2011-2022 走看看