• [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 eq r),取 (m = lfloor dfrac{l+r}{2} floor),则它的左右儿子节点记录的线段分别是 ([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 imes 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 imes (n+1))

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

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

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

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

    5.把该节点的标记下传,(l<x<=r)(l<=y<r)(p_0'=p_1'=0),概率 ((r-l) imes (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,'
    ');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);
    */
    
  • 相关阅读:
    Note/Solution 转置原理 & 多点求值
    Note/Solution 「洛谷 P5158」「模板」多项式快速插值
    Solution 「CTS 2019」「洛谷 P5404」氪金手游
    Solution 「CEOI 2017」「洛谷 P4654」Mousetrap
    Solution Set Border Theory
    Solution Set Stirling 数相关杂题
    Solution 「CEOI 2006」「洛谷 P5974」ANTENNA
    Solution 「ZJOI 2013」「洛谷 P3337」防守战线
    Solution 「CF 923E」Perpetual Subtraction
    KVM虚拟化
  • 原文地址:https://www.cnblogs.com/jiangxuancheng/p/14309705.html
Copyright © 2020-2023  润新知