( ext{Sooke} ’s solution is good)
点我
人在荒原, 见滴水而喜qwq。
教训
写短小函数时多看两眼, 可能会减少错误率。
前置芝士
如果研究每次操作后所有线段树具体形态, 未免太难啦qwq。
由于所有线段树的结构都一样, 所以建一棵参考用的线段树, 对其每个点 (u) 都记录在第 (i) 次操作后, 有多少棵线段树(当然不包括这棵参考用的)的 (“u”) 节点是有 (tag) 的, 记为 (f_{i,u}), 则第 (i) 次操作的答案就是 (sum_{u in T} f_{i,u}) ((T) 表示参考线段树的节点集)。
据 ( ext{Sooke}) 的题解, 对于一次特定的修改操作, 线段树的节点被分为 (5) 类, 不同类的节点的 (f) 的计算方式显然不相同。
如图。
再结合题目给出的伪代码
发现可以将线段树的节点分为这么几类:
- 第一类为蓝色点, 修改时被经过, 修改后不会有 (tag)。
- 第二类为紫色点, 修改时于此为终点, 修改后被打上 (tag)。
- 第三类为灰色点, 虽然没有被经过, 但是可能会得到一个 (tag)。
- 第四类为白色点, 修改前后其是否有 (tag) 的状态不会改变。
可以对每类节点分别维护 (f):
对于一类点, (f_{i,u} = f_{i-1,u} + 0)
对于二类点, (f_{i,u} = f_{i-1,u} + 2^{i-1})
对于四类点, (f_{i,u} = f_{i-1,u} + f_{i-1,u})
对于三类点, (f_{i,u} = f_{i-1,u} + ( 2^{i-1} - g_{i-1,u} ))
(g_{i,u}) 表示第 (i) 次操作后, 节点 (u) 到根一个 (tag) 都没有(包括 (u))的线段树个数。
对于 (g) 的维护, 有:
对于一类点, (g_{i,u} = g_{i-1,u} + 2^{i-1})
对于二类点, (g_{i,u} = g_{i-1,u} + 0)
对于三类点, (g_{i,u} = g_{i-1,u} + g_{i-1,u})
对于四类点, …………?
发现白色点(四类点)的 (g) 的转移还跟其父节点的类别有关, 而白色点的父节点又只有灰、紫两种, 所以白色点就要再分成两类。
这样, 点就分成了 (5) 类, 转移不难得出。 (再次 (orz ext{Sooke}) )
接下来就是如何维护线段树内 (f) 和的问题了。
发现每次操作时,除了白色点(原·四类点)要用懒标记维护外, 剩下的点直接在修改操作中维护就好。
代码不难(?)写出:
#include<bits/stdc++.h>
using namespace std;
#define li long long
const int mod = 998244353;
const int N = 1e6+15;
int n,m,id=0;
li jc2[100005];
int rt, tot, ch[N][2];
li sf[N], f[N], g[N], tf[N], tg[N];
inline li add(li x,li y) { x+=y; return x>=mod ? x-mod : x; }
inline li sub(li x,li y) { x-=y; return x>=0 ? x : x+mod;}
inline void ud(int u) { sf[u] = add(f[u], add(sf[ch[u][0]], sf[ch[u][1]])); }
void mlf(int u, li v) { tf[u]=tf[u]*v%mod; f[u]=f[u]*v%mod; sf[u]=sf[u]*v%mod; }
void mlg(int u, li v) { tg[u]=tg[u]*v%mod; g[u]=g[u]*v%mod; }
void ps(int u) {
if(tf[u]!=1) {
mlf(ch[u][0], tf[u]);
mlf(ch[u][1], tf[u]);
tf[u] = 1ll;
}
if(tg[u]!=1) {
mlg(ch[u][0], tg[u]);
mlg(ch[u][1], tg[u]);
tg[u] = 1ll;
}
}
void build(int &u,int l,int r) {
u = ++tot;
g[u] = tf[u] = tg[u] = 1ll;
if(l==r) return;
int mid=(l+r)>>1;
build(ch[u][0],l,mid);
build(ch[u][1],mid+1,r);
ud(u);
}
void modi(int u,int l,int r,int x,int y) {
if(x<=l&&r<=y) {
sf[u] = 2ll*sf[u]%mod;
sf[u] = sub(sf[u],f[u]);
sf[u] = add(sf[u],jc2[id]);
f[u] = add(f[u], jc2[id]);
//二类点
tf[u] = 2ll*tf[u]%mod;
//给四类点加tag
return;
}
ps(u);
g[u] = add(g[u], jc2[id]);
//一类点
int mid = (l+r) >> 1;
if(y<=mid) {
modi(ch[u][0],l,mid,x,y);
sf[ch[u][1]] = sf[ch[u][1]]*2ll % mod;
sf[ch[u][1]] = sub(sf[ch[u][1]], f[ch[u][1]]);
sf[ch[u][1]] = add(sf[ch[u][1]], jc2[id]);
sf[ch[u][1]] = sub(sf[ch[u][1]], g[ch[u][1]]);
f[ch[u][1]] = add(f[ch[u][1]], jc2[id]);
f[ch[u][1]] = sub(f[ch[u][1]], g[ch[u][1]]);
g[ch[u][1]] = g[ch[u][1]]*2ll%mod;
//右儿子是三类点
tf[ch[u][1]] = tf[ch[u][1]]*2ll % mod;
tg[ch[u][1]] = tg[ch[u][1]]*2ll % mod;
//给五类点加tag
} else if(x>mid) {
modi(ch[u][1],mid+1,r,x,y);
sf[ch[u][0]] = sf[ch[u][0]]*2ll % mod;
sf[ch[u][0]] = sub(sf[ch[u][0]], f[ch[u][0]]);
sf[ch[u][0]] = add(sf[ch[u][0]], jc2[id]);
sf[ch[u][0]] = sub(sf[ch[u][0]], g[ch[u][0]]);
f[ch[u][0]] = add(f[ch[u][0]], jc2[id]);
f[ch[u][0]] = sub(f[ch[u][0]], g[ch[u][0]]);
g[ch[u][0]] = g[ch[u][0]]*2ll%mod;
//左儿子是三类点
tf[ch[u][0]] = tf[ch[u][0]]*2ll % mod;
tg[ch[u][0]] = tg[ch[u][0]]*2ll % mod;
//给五类点加tag
} else {
modi(ch[u][0],l,mid,x,y);
modi(ch[u][1],mid+1,r,x,y);
}
ud(u);
}
int main()
{
scanf("%d%d", &n,&m);
jc2[0] = 1ll;
for(int i=1;i<=m;++i) jc2[i]=(jc2[i-1]*2ll)%mod;
build(rt, 1, n);
register int op=0, l=0, r=0;
while(m--)
{
scanf("%d", &op);
switch(op) {
case 1:
scanf("%d%d", &l,&r);
modi(rt,1,n,l,r);
++id;
break;
case 2:
printf("%lld
", sf[rt]);
break;
}
}
return 0;
}