【ZJOI2019】线段树【线段树上dp】【大讨论】

题意:有一个 [ 1 , n ] [1,n] [1,n] 的线段树和 m m m 个区间赋值操作,求任取一个操作的子集并按顺序在线段树上跑后线段树上有 lazy 标记的点的个数之和 模 998244353 998244353 998244353

n , m ≤ 1 0 5 n,m\leq 10^5 n,m105

真·线段树上 dp

考虑线段树的情况很复杂,所以大概率是强行讨论。

显然分结点考虑,即设 f ( u ) f(u) f(u) 为当前线段树上结点 u u u 有标记的概率。

然后对于一次操作,结点大概分为以下 4 4 4 类:

  1. 普通结点:该操作真实操作到的 log ⁡ \log log 个结点,这些结点操作后一定有标记。
  2. 文艺结点:普通线段树修改时经过但未覆盖的点。这些点即使有标记访问后也会被下放,所以一定没有标记。
  3. 二逼结点:在父结点只往兄弟结点递归时,可以得到父结点标记的结点。但它是否有标记需要讨论。
  4. 废物结点:和修改没有任何关系的结点。

然后开始讨论

  • 普通结点

f ( u ) ← f ( u ) + 1 2 f(u)\leftarrow \frac{f(u)+1}{2} f(u)2f(u)+1

  • 文艺结点

f ( u ) ← f ( u ) 2 f(u)\leftarrow \frac {f(u)}2 f(u)2f(u)

  • 二逼结点

f ( u ) ← f ( u ) + &#歪比巴卜 … … 2 f(u)\leftarrow\frac{f(u)+\texttt{\&\#歪比巴卜……}}{2} f(u)2f(u)+&#歪比巴卜

发现这个修改后有标记的概率不好搞。

然后考场上瞎传参乱搞,然后写崩了……

冷静分析我们实际上需要什么东西。

这个点有标记,当且仅当祖先传下来了一个标记,或者自己本来就有标记。总之就是 1 ∼ u 1\sim u 1u 的路径上至少有一个标记。

这个怎么搞呢?容斥?

实际上直接开个 g ( u ) g(u) g(u) 记一下就可以了……

重新推一下

  • 普通结点

自己被标记了,所以到根路径上一定也有标记。

f ( u ) ← f ( u ) + 1 2 f(u)\leftarrow \frac{f(u)+1}{2} f(u)2f(u)+1

g ( u ) ← g ( u ) + 1 2 g(u)\leftarrow \frac{g(u)+1}{2} g(u)2g(u)+1

  • 文艺结点

自己有标记也会被传下去。

f ( u ) ← f ( u ) 2 f(u)\leftarrow \frac {f(u)}2 f(u)2f(u)

g ( u ) ← g ( u ) 2 g(u)\leftarrow \frac {g(u)}2 g(u)2g(u)

  • 二逼结点

本身有标记当且仅当到根路径上有标记。因为祖先的标记都被传下来了,所以路径有标记相当于自己有标记。

f ( u ) ← f ( u ) + g ( u ) 2 f(u)\leftarrow \frac {f(u)+g(u)}2 f(u)2f(u)+g(u)

g ( u ) ← g ( u ) + g ( u ) 2 = g ( u ) g(u)\leftarrow \frac {g(u)+g(u)}2=g(u) g(u)2g(u)+g(u)=g(u)

  • 废物结点

然后你会发现还有两种小类:

  1. 底层结点

普通结点子树内被减掉的结点,标记不变,但到根路径一定有标记。

g ( u ) ← g ( u ) + 1 2 g(u)\leftarrow \frac {g(u)+1}2 g(u)2g(u)+1

  1. 路人结点

纯路人,没有任何变化。

然后 普通,文艺,二逼的结点是 O ( log ⁡ n ) O(\log n) O(logn) 的,直接暴力。底层结点可以打 lazy 标记,路人结点不管。

复杂度 O ( n log ⁡ n ) O(n\log n) O(nlogn)

#include <iostream>
#include <cstdio>
#include <cstring>
#include <cctype>
#define MAXN 100005
using namespace std;
inline int read()
{
	int ans=0;
	char c=getchar();
	while (!isdigit(c)) c=getchar();
	while (isdigit(c)) ans=(ans<<3)+(ans<<1)+(c^48),c=getchar();
	return ans;
}
typedef long long ll;
const int MOD=998244353,INV=(MOD+1)>>1;
int f[MAXN<<3],g[MAXN<<3],s[MAXN<<3];
int add[MAXN<<3],mul[MAXN<<3];
#define lc p<<1
#define rc p<<1|1
inline void update(int p){s[p]=((ll)f[p]+(ll)s[lc]+s[rc])%MOD;}
inline void pushlzy(int p,int m,int v){g[p]=((ll)g[p]*m+v)%MOD;mul[p]=(ll)mul[p]*m%MOD,add[p]=((ll)add[p]*m+v)%MOD;}
inline void pushdown(int p)
{
	if (add[p]||mul[p]>1)
	{
		pushlzy(lc,mul[p],add[p]),pushlzy(rc,mul[p],add[p]);
		add[p]=0,mul[p]=1;
	}
}
void modify(int p,int l,int r,int ql,int qr)
{
	if (ql<=l&&r<=qr) return (void)(f[p]=(f[p]+1ll)*INV%MOD,pushlzy(p,INV,INV),update(p));
	pushdown(p);
	f[p]=(ll)f[p]*INV%MOD,g[p]=(ll)g[p]*INV%MOD;
	int mid=(l+r)>>1;
	if (qr<=mid)
	{
		f[rc]=((ll)f[rc]+g[rc])*INV%MOD;
		update(rc);
		modify(lc,l,mid,ql,qr);
		return update(p);
	}
	if (ql>mid)
	{
		f[lc]=((ll)f[lc]+g[lc])*INV%MOD;
		update(lc);
		modify(rc,mid+1,r,ql,qr); 
		return update(p);
	}
	modify(lc,l,mid,ql,qr),modify(rc,mid+1,r,ql,qr);
	update(p);
}
int main()
{
	freopen("segment.in","r",stdin);
	freopen("segment.out","w",stdout);
	for (int i=0;i<(MAXN<<3);i++) mul[i]=1;
	int n=read(),cur=1;
	for (int m=read();m;m--)
	{
		int t=read();
		if (t==1)
		{
			int l,r;
			l=read(),r=read();
			modify(1,1,n,l,r);
			cur=cur*2%MOD;
		}
		else printf("%lld\n",(ll)s[1]*cur%MOD);
	}
	return 0;
}

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值