zoukankan      html  css  js  c++  java
  • [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);
    */
    
  • 相关阅读:
    [java学习]java聊天室通信原理
    竖变横表存储过程(万能型)
    到底是什么(反射,泛型,委托,泛型)
    删除表里重复记录两种方法
    三个SQL视图查出所有SQL Server数据库字典
    三种分页语句
    DBHelper
    SQL全局变量
    今天比较STRING和INT,很奇怪
    表之间数据交换与翻页存储过程
  • 原文地址:https://www.cnblogs.com/jiangxuancheng/p/14309705.html
Copyright © 2011-2022 走看看