[ZJOI2019]线段树

首先要注意到复制出的两颗棵段树一个被修改一个不被修改

所以实际上就是枚举了是否进行每个操作的\(2^m\)种情况

肯定不可能枚举出来

我们考虑对每个节点分别统计答案

设\(f_x\)表示当前节点\(x\)的所有情况下的\(Tag\)和

考虑每次加入一个操作对其\(Tag\)的影响

设当前为第\(t\)个操作

即修改的\(2^{t-i}\)棵线段树中

有多少棵的\(x\)节点的\(Tag\)是\(1\)

一共只有\(4\)种情况

  1. \(Tag\)直接被赋值为\(1\)(在这个点打标记)
  2. \(Tag\)直接被赋值为\(0\)(对该点进行了pushdown操作)
  3. \(Tag\)没有被修改(根本没有访问这个节点)
  4. \(Tag\)继承自父亲节点(对该点的父节点进行了pushdown操作但没有修改这个节点)

考虑\(f_x\)的改变量

对于前\(3\)种情况

\(f_x\)的改变是明显的

第一种\(f_x+=2^{t-1}\)

第二种\(f_x+=0\)

第三种\(f_x*=2\)

第四种比较复杂

\(f_x+=使这个点到根有至少一个节点的Tag为1的方案数\)

注意自己有标记也是可以的

因为只有\(1\)的标记会下传

设这个量为\(g_x\)

考虑\(g_x\)的维护

如果在线段树上对该点进行了pushdown操作

那么新增的\(2^{t-1}\)棵树的这个节点根的路径上都不会有\(Tag=1\)

\(g_x+=0\)

如果在这个点打了标记

这个点和它的子树的\(g\)都一定有\(Tag=1\)

这些节点的\(g+=2^{t-1}\)

剩下的节点到根的路径上的\(Tag\)情况不会改变

\(g_x*=2\)

考虑用线段树模拟维护\(f\)和\(g\)

因为涉及未访问节点

可以不修改这些节点

把访问的节点的值除\(2\)

算答案的时候乘上\(2^t\)

但这样\(g\)的修改不是很好维护

考虑每次修改的同时都会将其除\(2\)

所以用一个\(tag\)记录被修改了几次

这个是区间加法

\(g_{son}\)除去\(2^{tag_x}\)

然后加上了\(\frac{1}{2},\frac{1}{4},...,\frac{1}{2^{tag_x}}=1-2^{tag_x}\)

就可以直接pushdown

#include<bits/stdc++.h>

using namespace std;

#define gc c=getchar()
#define r(x) read(x)
#define ll long long

template<typename T>
inline void read(T&x){
    T k=1;char gc;x=0;
    while(!isdigit(c)){if(c=='-')k=-1;gc;}
    while(isdigit(c))x=x*10+c-'0',gc;x*=k;
}

const int N=1e5+7;
const int p=998244353;
const ll Inv=(p+1)>>1;

int Pow[N];
int sum=0,prod=1;

inline int add(int a,int b){
    return (a+=b)>=p?a-p:a;
}

inline int sub(int a,int b){
    return (a-=b)<0?a+p:a;
}

int f[N<<2],g[N<<2],tag[N<<2];

#define ls (rt<<1)
#define rs (rt<<1|1)

inline void pushdown(int rt){
    if(tag[rt]){
        g[ls]=add((ll)g[ls]*Pow[tag[rt]]%p,sub(1,Pow[tag[rt]]));
        g[rs]=add((ll)g[rs]*Pow[tag[rt]]%p,sub(1,Pow[tag[rt]]));
        tag[ls]+=tag[rt];
        tag[rs]+=tag[rt];
        tag[rt]=0;
    }
}

void modify(int rt,int l,int r,int x,int y){
    sum=sub(sum,f[rt]);
    f[rt]=f[rt]*Inv%p;
    g[rt]=g[rt]*Inv%p;
    if(x<=l&&r<=y){
        f[rt]=add(f[rt],Inv);
        g[rt]=add(g[rt],Inv);
    }
    else if(l>y||r<x){
        f[rt]=add(f[rt],g[rt]);
        g[rt]=add(g[rt],g[rt]);
    }
    sum=add(sum,f[rt]);
    if(l>y||r<x)return ;
    if(x<=l&&r<=y){
        ++tag[rt];
        return ;
    }
    pushdown(rt);
    int mid=(l+r)>>1;
    modify(ls,l,mid,x,y);
    modify(rs,mid+1,r,x,y);
}

int main(){
    freopen("segment.in","r",stdin);
    freopen("segment.out","w",stdout);
    int n,m;r(n),r(m);
    Pow[0]=1;
    for(int i=1;i<=m;++i){
        Pow[i]=Pow[i-1]*Inv%p;
    }
    while(m--){
        int opt,l,r;r(opt);
        if(opt==1){
            r(l),r(r);
            modify(1,1,n,l,r);
            prod=add(prod,prod);
        }
        else{
            printf("%lld\n",(ll)sum*prod%p);
        }
    }
}
上一篇:Luogu5327 ZJOI2019语言(树上差分+线段树合并)


下一篇:[ZJOI2019]麻将