[ZJOI2019]线段树

题目

题目描述
九条可怜是一个喜欢数据结构的女孩子,在常见的数据结构中,可怜最喜欢的就是线段树。

线段树的核心是懒标记,下面是一个带懒标记的线段树的伪代码,其中 tagtag 数组为懒标记:

其中函数 \operatorname{Lson}(Node)Lson(Node) 表示 NodeNode 的左儿子,\operatorname{Rson}(Node)Rson(Node) 表示 NodeNode 的右儿子。

现在可怜手上有一棵 [1,n][1,n] 上的线段树,编号为 11。这棵线段树上的所有节点的 tagtag 均为00。接下来可怜进行了 mm 次操作,操作有两种:

1\ l\ r1 l r,假设可怜当前手上有 tt 棵线段树,可怜会把每棵线段树复制两份(tagtag 数组也一起复制),原先编号为 ii 的线段树复制得到的两棵编号为 2i-12i−1 与 2i2i,在复制结束后,可怜手上一共有 2t2t 棵线段树。接着,可怜会对所有编号为奇数的线段树进行一次 \operatorname{Modify}(root,1,n,l,r)Modify(root,1,n,l,r)。

22,可怜定义一棵线段树的权值为它上面有多少个节点 tagtag 为 11。可怜想要知道她手上所有线段树的权值和是多少。

输入格式
第一行输入两个整数 n,mn,m 表示初始区间长度和操作个数。

接下来 mm 行每行描述一个操作,输入保证 1 \le l \le r \le n1≤l≤r≤n。

输出格式
对于每次询问,输出一行一个整数表示答案,答案可能很大,对 998244353998244353 取模后输出即可。

输入输出样例
输入 #1复制
5 5
2
1 1 3
2
1 3 5
2
输出 #1复制
0
1
6
说明/提示
[1,5] 上的线段树如下图所示:

在第一次询问时,可怜手上有一棵线段树,它所有点上都没有标记,因此答案为 00。

在第二次询问时,可怜手上有两棵线段树,按照编号,它们的标记情况为:

点 [1,3][1,3] 上有标记,权值为 11。
没有点有标记,权值为 00。
因此答案为 11。

在第三次询问时,可怜手上有四棵线段树,按照编号,它们的标记情况为:

点 [1,2],[3,3],[4,5][1,2],[3,3],[4,5] 上有标记,权值为 33。
点 [1,3][1,3] 上有标记,权值为 11。
点 [3,3],[4,5][3,3],[4,5] 上有标记,权值为 22。
没有点有标记,权值为 00。
因此答案为 66。

思路

dp+大力分类讨论
对于一个操作 [ q l , q r ] [ql,qr] [ql,qr] ,我们把线段树上所有节点分成四类考虑

1.如果 q l ≤ l & a m p ; & a m p ; q r ≥ r ql\leq l\&\&qr\geq r qll&&qrr ,且该节点被访问到,那么不管之前操作序列怎么样,只要执行了当前操作它必为 1 1 1 ,所以它的值加上 2 t 2^t 2t ,其中 t t t 为已经执行的操作数目
2.如果节点没有被访问到过,那么这个操作是否执行都对它们的 t a g tag tag 没有影响,所以它们的值乘 2 2 2
3.如果 [ q l , q r ] ∩ [ l , r ] ≠ ∅ [ql,qr]\cap[l,r]\neq \emptyset [ql,qr][l,r]= 且不属于 1 1 1 ,那么如果当前操作执行它们的 t a g tag tag 显然全没了,所以它们的值不变
4.还有一种情况 [ q l , q r ] ∩ [ l , r ] = ∅ [ql,qr]\cap [l,r]=\emptyset [ql,qr][l,r]= ,且当前节点的父亲属于 3 3 3 ,但是当前节点不属于 1 1 1 。这种情况下当前节点的值要加上 f i f_i fi ,其中 f i f_i fi 表示的是“能够使当前节点到根的所有节点中存在 t a g tag tag 1 1 1 的点的序列个数”
先不考虑 f i f_i fi 怎么维护。那么我们只要在线段树上遍历区间,对应地打上给节点加上值(比如 1 1 1 ),给节点打乘法标记就行了。全局的答案可以先减去所有遍历到的节点的答案,乘上 2 2 2 之后再把遍历到的节点的答案加上去就好了
然后关键是 f i f_i fi 还得维护啊……还是得分类讨论……
1.如果 q l ≤ l & a m p ; & a m p ; q r ≥ r ql\leq l\&\&qr\geq r qll&&qrr ,不管当前节点是否被访问过。因为当前操作执行之后它们到根节点的路径上显然会有 t a g tag tag 1 1 1 ,所以这些节点每一个都要加上 2 t − 1 2^{t-1} 2t1
2.如果是之前的 3 3 3 情况,那么显然根节点到它的所有 t a g tag tag 都没了,所以 f i f_i fi 不变
3.除此之外的所有情况,包括之前的 4 4 4 ,显然当前操作不管是否执行都对它们的 f i f_i fi 没有影响,所以它们的 f i f_i fi 都要乘 2 2 2

代码

#include<bits/stdc++.h>
using namespace std;
const int N=1e5+10;
typedef long long ll;
const ll mod=998244353;
const ll iv2=(mod+1)/2;ll imi[N];ll dmi[N];ll mi[N];int n;int m;
# define md(x) (x=(x>=mod)?x-mod:x)
struct linetree
{
    ll sum;ll dp[N<<2];ll fdp[N<<2];int l[N<<2];int r[N<<2];int add[N<<2];
    int qu1[N];int hd1;int qu2[N];int hd2;int qu3[N];int hd3;
    int qu[N];int hd;int tl;
    void chan(int p,int tmp)
    {sum+=mod-dp[p];md(sum);dp[p]=(dp[p]+tmp)*iv2%mod;sum+=dp[p];md(sum);}
    void pd(int p)
    {
        if(add[p]!=0)
        {
            fdp[p<<1]=(fdp[p<<1]+dmi[add[p]])*imi[add[p]]%mod;
            fdp[p<<1|1]=(fdp[p<<1|1]+dmi[add[p]])*imi[add[p]]%mod;
            add[p<<1]+=add[p];add[p<<1|1]+=add[p];add[p]=0;
        }
    }void extract(int dl,int dr)
    {
        hd=1;tl=0;qu[++tl]=1;hd1=hd2=hd3=0;
        while(hd<=tl)
        {
            int nw=qu[hd++];
            if(dl<=l[nw]&&r[nw]<=dr){qu2[++hd2]=nw;continue;}
            if(r[nw]<dl||l[nw]>dr){qu3[++hd3]=nw;continue;}
            qu1[++hd1]=nw;pd(nw);qu[++tl]=nw<<1;qu[++tl]=nw<<1|1;
        }
    }void modify(int dl,int dr)
    {
        extract(dl,dr);for(int i=1;i<=hd1;i++)chan(qu1[i],0);
        for(int i=1;i<=hd2;i++)chan(qu2[i],1);
        for(int i=1;i<=hd2;i++)add[qu2[i]]++,fdp[qu2[i]]=(fdp[qu2[i]]+1)*iv2%mod;
        for(int i=1;i<=hd3;i++)chan(qu3[i],fdp[qu3[i]]);
        for(int i=1;i<=hd1;i++)fdp[qu1[i]]=fdp[qu1[i]]*iv2%mod;

    }void build(int p,int pl,int pr)
    {
        if(pl>pr)return;l[p]=pl;r[p]=pr;if(pl==pr)return;
        int mid=(pl+pr)>>1;build(p<<1,pl,mid);build(p<<1|1,mid+1,pr);
    }
}lt;
int main()
{
    imi[0]=1;for(int i=1;i<N;i++)imi[i]=imi[i-1]*iv2%mod;
    mi[0]=1;for(int i=1;i<N;i++)mi[i]=mi[i-1]*2%mod;
    dmi[0]=0;for(int i=1;i<N;i++)dmi[i]=(mi[i]+mod-1)%mod;
    scanf("%d%d",&n,&m);lt.build(1,1,n);int tot=0;
    for(int i=1,tp,l,r;i<=m;i++)
    {
        scanf("%d",&tp);
        if(tp==1)scanf("%d%d",&l,&r),lt.modify(l,r),tot++;
        else printf("%lld\n",lt.sum*mi[tot]%mod);
    }return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值