[ZJOI2019]线段树

[ZJOI2019]线段树

如果你有幸做过 [ZJOI2017] 线段树 的话,那么你看到这道题的第一反应肯定是套那个结论。

但是这题并不是一道优美的结论题,所以试图找出 tag 下传的规律是没用滴。


首先考虑修改修改的本质。假设有 \(m\) 个操作 \(1\)

那么产生的 \(2^m\) 个线段树每个对应 \(m\) 个操作的子集。

由于这题的 tag 下传很复杂,并没有一个优美简单的结论让我们快速计算。所以我们可以考虑使用 dp 来解决复杂的 tag 下传情况。

一种朴素的想法是设 \(f_i\) 表示 \(i\) 这个 tag 为 \(1\) 时的方案数。不难发现答案就是 \(\sum f_i\)

当然我们也可以设 \(f_i\) 表示 \(i\) 这个 tag 为 \(1\)概率当然你也可以理解成比例。那么答案就是 \(2^m\sum f_i\)。(\(m\) 表示该次查询前进行的修改操作次数)

本文按下面那种定义进行推导。

我们发现一个点如果没有 tag,但是它的某个祖先也有 tag,那么这个点还有是可能在某一次操作后获得 tag,那么我们还需要记一个数组 \(g_i\) 表示 \(i\) 到根节点的链上存在某个点 tag 为 \(1\) 的概率。(包括 \(i\)

接下来手搓一下下传标记的操作,发现可以按照三种点分类。

  1. 此次操作会进行修改的点。即能够进入伪代码 \(13\) 行 if 的点。
  2. 此次操作不会进行修改的点,但是其儿子存在进行修改的点。即不会进入伪代码 \(10,13\) 行 if 的点。
  3. 此次操作不会进行修改的点,但是它的父亲是 \(2\) 类点。即进入伪代码 \(10\) 行 if 的点。

接下来大力分类讨论:


一类点

观察复制后生成的两棵新子树,修改的那棵中该点的 tag 一定为 \(1\)。不修改的那棵该点 tag 不变。

那么 \(f_i=\dfrac{f_i+1}{2}\)

并且以该点为根的子树内的所有点的 \(g\) 值也有变化,因为它们到根的链上存在点 tag 为 \(1\) 了。这是一个区间修改操作,由于原树本来就是一颗线段树,所以我们打 tag 就行了(这不是题目的 tag,指你平时线段树用的那个 tag)

观察复制后的两颗新子树,修改的那棵中到根的链上一定存在点 tag 为 \(1\),没修改的不变。

那么 \(g_i=\dfrac{g_i+1}{2}\)

如果做了 \(k\) 次。那么显然有 \(g_i=\dfrac{(g_i+2^k-1)}{2^k}\)。根据这个下放 tag 完成区间修改即可。


二类点

由于 tag 会下传,所以作为二类点,不仅他们本身不会有 tag 为 \(1\) 的情况,他们到根的链上也不会有。

所以 \(f_i=\dfrac{f_i+1}{2},g_i=\dfrac{g_i+1}{2}\)


三类点

他们的 tag 不会被直接修改,但是可能存在它们父亲 tag 下传的情况。

所以 \(f_i=\dfrac{f_i+g_i}{2}\)

由于该点的父亲的 tag 如果下传都会传到该点,所以以该点为根的子树内的 \(g\) 值不变。


然后模拟转移即可。复杂度 \(O(m\log n)\)

代码如下:

#include<bits/stdc++.h>
#define ll long long
using namespace std;
const int MAXN = 2e5+5;
const int MOD = 998244353;
int n,m;
ll inv,P[MAXN],IV[MAXN];
ll qpw(ll x,ll b){ll r=1;for(ll t=x,c=1;c<=b;c<<=1,t=t*t%MOD)if(c&b) r=r*t%MOD;return r;}
struct Tree
{
	#define ls(k) (k<<1)
	#define rs(k) (k<<1|1)
	ll f[MAXN<<2],g[MAXN<<2],v[MAXN<<2],tg[MAXN<<2];
	void pd(int k)
	{
		if(!tg[k]) return ;
		tg[ls(k)]+=tg[k];tg[rs(k)]+=tg[k];
		g[ls(k)]=(g[ls(k)]+P[tg[k]]-1+MOD)%MOD*IV[tg[k]]%MOD;
		g[rs(k)]=(g[rs(k)]+P[tg[k]]-1+MOD)%MOD*IV[tg[k]]%MOD;
		tg[k]=0;
	}
	void pu(int k){v[k]=(v[ls(k)]+v[rs(k)]+f[k])%MOD;}
	void upd(int le,int ri,int k,int l,int r)
	{
		if(r<le||l>ri)
		{
			f[k]=(f[k]+g[k])*inv%MOD;pu(k);
			return ;
		}
		if(le<=l&&r<=ri)
		{
			tg[k]++;f[k]=(f[k]+1)*inv%MOD;g[k]=(g[k]+1)*inv%MOD;pu(k);
			return ;
		}
		int mid=l+r>>1;pd(k);
		f[k]=f[k]*inv%MOD;g[k]=g[k]*inv%MOD;
		upd(le,ri,ls(k),l,mid);upd(le,ri,rs(k),mid+1,r);
		pu(k);
	}
}T;
int main()
{
	freopen("segment.in","r",stdin);
	freopen("segment.out","w",stdout);
	scanf("%d %d",&n,&m);
	P[0]=IV[0]=1;inv=qpw(2,MOD-2);
	for(int i=1;i<=m;++i) P[i]=P[i-1]*2%MOD,IV[i]=IV[i-1]*inv%MOD;
	int c=0;
	for(int i=1;i<=m;++i)
	{
		int opt,l,r;
		scanf("%d",&opt);
		if(opt==2) printf("%lld\n",T.v[1]*P[c]%MOD);
		else scanf("%d %d",&l,&r),++c,T.upd(l,r,1,1,n);
	}
	return 0;
}
posted @ 2022-04-20 16:10  夜空之星  阅读(27)  评论(0编辑  收藏  举报