luogu P5280 [ZJOI2019]线段树

传送门

这题好妙啊

首先一个明显的想法是统计某个点权值为\(0/1\)的方案数,但是这样子无法转移,因为可能一个点的祖先为\(1\),然后这个点会被祖先\(pushdown\)\(1\),然而我们并不知道祖先的状态,,,

那就把祖先加入状态啊.设\(f_{x,0/1/2}\)为点\(x\),自己和所有祖先都是\(0\)/自己是\(0\),有祖先是\(1\)/自己是\(1\)的方案.然后每次转移要先向自己转移(因为会复制一份一样的),剩下转移有5种情况

  • \(x\)是被\(pushdown\)的点,没有被标记:那么这个点以及所有祖先都会变成0,也就是\[\begin{matrix}f_{x,0}\rightarrow f_{x',0}\\f_{x,1}\rightarrow f_{x',0}\\f_{x,2}\rightarrow f_{x',0}\end{matrix}\]
  • \(x\)被标记:那么这个点变成\(1\),所有祖先都会变成\(0\),也就是\[\begin{matrix}f_{x,0}\rightarrow f_{x',2}\\f_{x,1}\rightarrow f_{x',2}\\f_{x,2}\rightarrow f_{x',2}\end{matrix}\]
  • \(x\)的祖先被标记:那么这个点对应祖先状态就是\(1\),也就是\[\begin{matrix}f_{x,0}\rightarrow f_{x',1}\\f_{x,1}\rightarrow f_{x',1}\\f_{x,2}\rightarrow f_{x',2}\end{matrix}\]
  • \(x\)的父亲被\(pushdown\),没有递归到自己:所有祖先都会变成\(0\),并且这个点如果之前祖先状态就是\(1\),自己也会变成\(1\),也就是\[\begin{matrix}f_{x,0}\rightarrow f_{x',0}\\f_{x,1}\rightarrow f_{x',2}\\f_{x,2}\rightarrow f_{x',2}\end{matrix}\]
  • 其他情况:\[\begin{matrix}f_{x,0}\rightarrow f_{x',0}\\f_{x,1}\rightarrow f_{x',1}\\f_{x,2}\rightarrow f_{x',2}\end{matrix}\]

考虑优化,第一种,第二种,第四种可以在遍历线段树的时候直接转移,然后一个点被标记,那么它子树内其他点都会进行第三种转移,注意这个转移可以写成矩阵的形式,所以可以在以dfn序为下标的线段树上对子树对应的区间乘上转移矩阵在线段树每个节点维护一个转移标记,给当前点标记乘上转移矩阵,然后遍历的时候记得下放标记.还有第五种转移,其实如果把转移的系数从\(1\)改成\(\frac{1}{2}\),然后把\(2\)乘在变量\(sm\),那么第五个转移其实不用进行,然后计算答案就是\(sm\sum_{x}f_{x,2}\)

#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#define LL long long
#define db double

using namespace std;
const int N=1e5+10,mod=998244353,inv2=499122177;
int rd()
{
    int x=0,w=1;char ch=0;
    while(ch<'0'||ch>'9'){if(ch=='-') w=-1;ch=getchar();}
    while(ch>='0'&&ch<='9'){x=(x<<3)+(x<<1)+(ch^48);ch=getchar();}
    return x*w;
}
void ad(int &x,int y){x+=y,x-=x>=mod?mod:0;}
void dc(int &x,int y){x-=y,x+=x<0?mod:0;}
struct matrix
{
    int a[3][3];
    matrix(){memset(a,0,sizeof(a));}
    matrix operator * (const matrix &bb) const
    {
        matrix an;
        for(int i=0;i<3;++i)
            for(int j=0;j<3;++j)
            {
                LL nw=0;
                for(int k=0;k<3;++k) nw+=1ll*a[i][k]*bb.a[k][j];
                an.a[i][j]=nw%mod;
            }
        return an;
    }
}f[N<<2],tg[N<<2],t1,t2,t3,t4,I;
int n,q,nm=1,sm,tt,ch[N<<2][2];
#define mid ((l+r)>>1)
void bui(int o,int l,int r)
{
    f[o].a[0][0]=1,tg[o]=I;
    if(l==r) return;
    bui(ch[o][0]=++tt,l,mid),bui(ch[o][1]=++tt,mid+1,r);
}
void trans(int o,matrix x)
{
    dc(sm,f[o].a[0][2]);
    f[o]=f[o]*x;
    ad(sm,f[o].a[0][2]);
}
void psdn(int o){trans(ch[o][0],tg[o]),tg[ch[o][0]]=tg[ch[o][0]]*tg[o],trans(ch[o][1],tg[o]),tg[ch[o][1]]=tg[ch[o][1]]*tg[o],tg[o]=I;}
void wk(int o,int l,int r,int ll,int rr)
{
    if(ll<=l&&r<=rr)
    {
        trans(o,t1);
        tg[o]=tg[o]*t4;
        return;
    }
    psdn(o);
    trans(o,t2);
    if(ll<=mid)
    {
        wk(ch[o][0],l,mid,ll,rr);
        if(rr<=mid) trans(ch[o][1],t3);
    }
    if(rr>mid)
    {
        wk(ch[o][1],mid+1,r,ll,rr);
        if(ll>mid) trans(ch[o][0],t3);
    }
}

int main()
{
    for(int i=0;i<3;++i) I.a[i][i]=1,t1.a[i][i]=t2.a[i][i]=t3.a[i][i]=t4.a[i][i]=inv2;
    ad(t1.a[0][2],inv2),ad(t1.a[1][2],inv2),ad(t1.a[2][2],inv2);
    ad(t2.a[0][0],inv2),ad(t2.a[1][0],inv2),ad(t2.a[2][0],inv2);
    ad(t3.a[0][0],inv2),ad(t3.a[1][2],inv2),ad(t3.a[2][2],inv2);
    ad(t4.a[0][1],inv2),ad(t4.a[1][1],inv2),ad(t4.a[2][2],inv2);
    n=rd(),q=rd();
    bui(++tt,1,n);
    while(q--)
    {
        int op=rd();
        if(op==1)
        {
            nm=2ll*nm%mod;
            int l=rd(),r=rd();
            wk(1,1,n,l,r);
        }
        else printf("%lld\n",1ll*nm*sm%mod);
    }
    return 0;
}

转载于:https://www.cnblogs.com/smyjr/p/10738425.html

你可能感兴趣的:(luogu P5280 [ZJOI2019]线段树)