[ZJOI2019]线段树 solution?
\(\text{Sooke} ’s \ \ solution \ \ is \ \ good\)
点我
人在荒原, 见滴水而喜qwq。
教训
写短小函数时多看两眼, 可能会减少错误率。
前置芝士
如果研究每次操作后所有线段树具体形态, 未免太难啦qwq。
由于所有线段树的结构都一样, 所以建一棵参考用的线段树, 对其每个点 \(u\) 都记录在第 \(i\) 次操作后, 有多少棵线段树(当然不包括这棵参考用的)的 \(“u”\) 节点是有 \(tag\) 的, 记为 \(f_{i,u}\), 则第 \(i\) 次操作的答案就是 \(\sum_{u \in T} f_{i,u}\) (\(T\) 表示参考线段树的节点集)。
据 \(\text{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 \ \ \text{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\n", sf[rt]);
break;
}
}
return 0;
}