[ZJOI2019]线段树 solution?

\(\text{Sooke} ’s \ \ solution \ \ is \ \ good\)
点我


人在荒原, 见滴水而喜qwq。

教训
写短小函数时多看两眼, 可能会减少错误率。

前置芝士

\[\sum_{x\in S} x = \sum_x x*(x在S中出现的次数) \]


如果研究每次操作后所有线段树具体形态, 未免太难啦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;
}
posted @ 2020-06-19 10:01  xwmwr  阅读(135)  评论(1编辑  收藏  举报