题目
题目描述
九条可怜是一个喜欢数据结构的女孩子,在常见的数据结构中,可怜最喜欢的就是线段树。
线段树的核心是懒标记,下面是一个带懒标记的线段树的伪代码,其中 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
ql≤l&&qr≥r ,且该节点被访问到,那么不管之前操作序列怎么样,只要执行了当前操作它必为
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
ql≤l&&qr≥r ,不管当前节点是否被访问过。因为当前操作执行之后它们到根节点的路径上显然会有
t
a
g
tag
tag 为
1
1
1 ,所以这些节点每一个都要加上
2
t
−
1
2^{t-1}
2t−1
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;
}