[ZJOI2020]传统艺能 题解 [DP+矩阵+分类讨论]

[ZJOI2020]传统艺能

Description:

​ Bob 喜欢线段树。

​ 众所周知,ZJOI 的第二题有很多线段树。

​ Bob 有一棵根为 \([1,n]\) 的广义线段树。Bob 需要在这个线段树上执行 \(k\) 次区间懒标记操作,每次操作会等概率地从 \([1,n]\) 的所有 \(\dfrac{n(n+1)}{2}\) 个子区间中随机选择一个。对于所有在该次操作中被访问到的非叶子节点,Bob 会将这个点上的标记下推;而对于所有叶子节点(即没有继续递归的节点),Bob 会给这个点打上标记。

​ Bob 想知道,\(k\) 次操作之后,有标记的节点的期望数量是多少。

​ 【具体定义】

​ 线段树:线段树是一棵每个节点上都记录了一个线段的二叉树。根节点记录的线段是 \([1,n]\)。对于每个节点,若它记录的线段是\([l,r]\)\(l \neq r\),取 \(m = \lfloor \dfrac{l+r}{2} \rfloor\),则它的左右儿子节点记录的线段分别是 \([l,m]\)\([m+1,r]\);若 \(l = r\),则它是叶子节点。

​ 广义线段树:在广义的线段树中,\(m\) 不要求恰好等于区间的中点,但是 \(m\) 还是必须满足 \(l \leq m < r\)的。不难发现在广义的线段树中,树的深度可以达到 \(O(n)\) 级别。

​ 线段树的核心是懒标记,下面是一个带懒标记的广义线段树的伪代码,其中 tag 数组为懒标记:

img

​ 注意,在处理叶子节点时,一旦他获得了一个标记,那么这个标记会一直存在。

​ 你也可以这么理解题意:有一棵广义线段树,每个节点有一个 \(m\) 值。一开始 tag 数组均为 \(0\),Bob 会执行 \(k\) 次操作,每次操作等概率随机选择区间 \([l,r]\) 并执行 MODIFY(root,1,n,l,r);。 最后所有 Node 中满足 tag[Node]=1 的期望数量就是需要求的值。

Input:

​ 第一行输入两个整数 \(n, k\)

​ 接下来输入一行包含 \(n - 1\) 个整数 \(a_i\):按照先序遍历的顺序,给出广义线段树上所有非叶子节点的划分位置 \(m\)。你也可以理解为从只有 \([1,n]\) 根节点开始,每次读入一个整数后,就将当前包含这个整数的节点做一次拆分,最后获得一棵有 \(2n - 1\) 个节点的广义线段树。

​ 保证给定的 \(n - 1\) 个整数是一个排列,不难发现通过这些信息就能唯一确定一棵 \([1,n]\) 上的广义线段树。

Output:

​ 输出一行一个整数,代表期望数量对 \(p = 998244353\) 取模后的结果。即,如果期望数量的最简分数表示为 \(\dfrac{a}{b}\),你需要输出一个整数 \(c\) 满足 \(c \times b \equiv a \pmod p\)

Sample Input1:

3 1
1 2

Sample Output1:

166374060

Sample Input2:

5 4
2 1 3 4

Sample Output2:

320443836

Hint:

样例输入输出 \(3\) 见下发文件。

样例解释 \(1\)

输入的线段树为 \([1, 3], [1, 1], [2, 3], [2, 2], [3, 3]\)

若操作为 \([1, 1]/[2, 2]/[3, 3]/[2, 3]/[1, 3]\),标记个数为 \(1\)。若操作为 \([1, 2]\),标记个数为 \(2\)。故答案为 \(\dfrac{7}{6}\)

测试点 n k 其他约定
1 \(\leq 10\) \(\leq 4\)
2 \(\leq 10\) \(\leq 100\)
3 \(\leq 5\)
4 \(=1\)
5 \(=32\) 输入的线段树为完全二叉树
6 \(=64\) 输入的线段树为完全二叉树
7 \(=4096\) 输入的线段树为完全二叉树
8 \(\leq 5000\) 每个 \(m\) 均在 \([l, r - 1]\) 内均匀随机
9 \(\leq 100000\)
10

对于 \(100\%\) 的数据,\(1 \leq n \leq 200000, 1 \leq k \leq 10^9\)

附件下载

segment.zip \(423.35KB\)

题目分析:

\([l,r]\)为线段树中一个节点储存的区间;

\([L,R]\)为它的父亲节点;

\([x,y]\)为选中的区间;

\(p_0\)表示本身有标记的概率,\(p_1\)表示本身或祖先有标记的概率

PS:以下概率都乘上了 \(n \times (n+1)\)

1.与\([L,R]\)无交集,\(y<L\)\(x>R\)\(p_0'=p_0,p_1'=p_1\),概率 \(L \times (L-1) + (n-R+1) \times (n-R)\) ;

2.与\([l,r]\)无交集且与\([L,R]\)有交集,则祖先标记下传到该节点,\(L<=y<l\)\(r<x<=R\)\(p_0'=p_1'=p_1\),概率 \((2n-r-R+1) \times (R-r) + (L+l-1) \times (l-L)\);

3.在祖先上打了一个标记,\(x<=L\)\(y>=R\)\(p_0'=p_0,p_1'=1\),概率 \(2L \times (n-R+1)\);

4.在该节点打了一个标记,\(x<=l,r<=y<R\)\(L<x<=l,r<=y\)\(p_0'=p_1'=1\),概率 \(2(l \times (R-r) + (l-L) \times (n-r+1))\);

5.把该节点的标记下传,\(l<x<=r\)\(l<=y<r\)\(p_0'=p_1'=0\),概率 \((r-l) \times (2n-r+l+1)\);

于是我们列出矩阵,矩阵快速幂跑一下就完事了。

PS:我写了很多诡异无用的卡常,仅供参考,留给读者自行思考(当然不卡常也能过,但是人要有信仰不是吗)

代码如下(马蜂很丑,不喜勿喷)——

#include<bits/stdc++.h>
#define Tp template<typename T>
#define Ts template<typename Ty,typename... Ar>
#define Reg register
#define RI Reg int
#define Con const
#define CI Con int&
#define I inline
#define W while
#define maxn 200005
#define LL long long
using namespace std;
int inv,n,K,anss;const int p=998244353;
inline int power(int x,int y){int z=1;while(y){if(y&1) z=1ll*z*x%p;y>>=1,x=1ll*x*x%p;}return z;}
struct node{
	int g[5][5];
}tmp,res,ans;
inline void mul(node x,node y,node &z){
	z.g[1][1]=1ll*x.g[1][1]*y.g[1][1]%p;z.g[2][2]=1ll*x.g[2][2]*y.g[2][2]%p;
	z.g[1][2]=(1ll*x.g[1][1]*y.g[1][2]+1ll*x.g[1][2]*y.g[2][2])%p;
	z.g[1][3]=(1ll*x.g[1][1]*y.g[1][3]+1ll*x.g[1][2]*y.g[2][3]+x.g[1][3])%p;
	z.g[2][3]=(1ll*x.g[2][2]*y.g[2][3]+x.g[2][3])%p,z.g[3][3]=1;
//	每天一个卡常 trick1
//	for(register int i=1;i<=2;i++) for(register int j=1;j<=3;j++) z.g[i][j]=0;z.g[3][3]=1;
//	for(register int i=1;i<=2;i++) for(register int j=1;j<=3;j++) for(register int k=1;k<=3;k++)
//	z.g[i][j]+=1ll*x.g[i][k]*y.g[k][j]%p,(z.g[i][j]>=p)&&(z.g[i][j]-=p);
}
inline void qpow(int m){
	for(register int i=1;i<=3;i++) for(register int j=1;j<=3;j++) if(i==j) ans.g[i][j]=1;else ans.g[i][j]=0;
	while(m){if(m&1) mul(ans,res,tmp),ans=tmp;m>>=1,mul(res,res,tmp),res=tmp;}
}
class FileInputOutput
{
	private:
		static const int S=1<<21;
		#define tc() (A==B&&(B=(A=Fin)+fread(Fin,1,S,stdin),A==B)?EOF:*A++)
		#define pc(ch) (Ftop!=Fend?*Ftop++=ch:(fwrite(Fout,1,S,stdout),*(Ftop=Fout)++=ch))
		char Fin[S],Fout[S],*A,*B,*Ftop,*Fend; int pt[25];
	public:
		FileInputOutput(void) { Ftop=Fout; Fend=Fout+S; }
		Tp inline void read(T& x)
		{
			x=0; char ch; while (!isdigit(ch=tc()));
			while (x=(x<<3)+(x<<1)+(ch&15),isdigit(ch=tc()));
		}
		Tp inline void write(T x,const char& ch)
		{
			if (x<0) pc('-'),x=-x; RI ptop=0; while (pt[++ptop]=x%10,x/=10);
			while (ptop) pc(pt[ptop--]+48); pc(ch);
		}
		inline void flush(void)
		{
			fwrite(Fout,1,Ftop-Fout,stdout);
		}
		#undef tc
		#undef pc
}F;
inline void get(int L,int R,int l,int r){
	if(L){
		res.g[2][2]=(1ll*L*(L-1)+1ll*(n-R+1)*(n-R)+1ll*(2*n-r-R+1)*(R-r)+1ll*(L+l-1)*(l-L))%p;res.g[1][1]=(1ll*L*(L-1)+1ll*(n-R+1)*(n-R)+2ll*L*(n-R+1))%p;
		res.g[1][2]=(1ll*(2*n-r-R+1)*(R-r)+1ll*(L+l-1)*(l-L))%p;res.g[1][3]=2ll*(1ll*l*(R-r)+1ll*(l-L)*(n-r+1))%p;res.g[2][3]=2ll*(1ll*L*(n-R+1)+1ll*l*(R-r)+1ll*(l-L)*(n-r+1))%p;res.g[3][3]=1;
		res.g[1][1]=1ll*res.g[1][1]*inv%p;res.g[1][2]=1ll*res.g[1][2]*inv%p;res.g[1][3]=1ll*res.g[1][3]*inv%p;res.g[2][3]=1ll*res.g[2][3]*inv%p;res.g[2][2]=1ll*res.g[2][2]*inv%p;
//		for(register int j=1;j<=3;j++) for(register int k=1;k<=3;k++) 
//		if(res.g[j][k]&&j!=3||k!=3) res.g[j][k]=1ll*res.g[j][k]*inv%p;卡常 trick2 
		qpow(K),anss+=ans.g[1][3],(anss>=p)&&(anss-=p);
	}
	if(l==r) return;int mid;F.read(mid);get(l,r,l,mid),get(l,r,mid+1,r);
}
int main(){
//	freopen("data.in","r",stdin);
	F.read(n),F.read(K);inv=power(1ll*n*(n+1)%p,p-2);anss=2ll*inv%p;get(0,n,1,n);
	F.write(anss,'\n');return F.flush(),0;
}
/*
[l,r]为线段树中一个节点储存的区间; 
[L,R]为它的父亲节点; 
[x,y]为选中的区间; 
p0表示本身有标记的概率,p1表示本身或祖先有标记的概率 
以下概率都乘上了 n(n+1)
1.与[L,R]无交集,y<L or x>R ,p0'=p0,p1'=p1,概率 L(L-1)+(n-R+1)(n-R) ;
2.与[l,r]无交集且与[L,R]有交集,则祖先标记下传到该节点,L<=y<l or r<x<=R,p0'=p1'=p1,概率 (2n-r-R+1)(R-r)+(L+l-1)(l-L);
3.在祖先上打了一个标记,x<=L且y>=R,p0'=p0,p1'=1,概率 2L(n-R+1);
4.在该节点打了一个标记,x<=l,r<=y<R or L<x<=l,r<=y,p0'=p1'=1,概率 2(l(R-r)+(l-L)(n-r+1));
5.把该节点的标记下传,l<x<=r or l<=y<r,p0'=p1'=0,概率 (r-l)(2n-r+l+1);
*/
posted @ 2021-01-21 18:48  OdtreePrince  阅读(175)  评论(0编辑  收藏  举报