zoukankan      html  css  js  c++  java
  • Codeforces 1392H

    Codeforces 题面传送门 & 洛谷题面传送门

    真·两天前刚做过这场的 I 题,今天模拟赛就考了这场的 H 题,我怕不是预言带师

    提供一种奇怪的做法,来自于同机房神仙们,该做法不需要 Min-Max 容斥,也不用爆推组合数,只需要比较强的眼力的初中数学求解二元一次方程组知识

    期望题没往 Min-Max 容斥的方向去想,不愧是我(大雾

    首先我们先考虑一些复杂度比较高的多项式复杂度做法。注意到对于任何一个局面而言,我们并不用关心 (S) 里究竟具体有哪些数,也不用关心牌堆中具体有哪些数字牌,我们只用关心有多少数在 (S) 中,以及牌堆中还剩多少张牌。因此我们可以设 (dp_{i,j}) 表示牌堆中还剩 (i) 张牌,(S) 中已经有 (j) 个数,期望还需多少步,那么:

    • 对于 (j e n),下一次摸出一张牌,有三种可能:

      • 摸出一张数字牌,且不在 (S) 中,概率 (dfrac{n-j}{i+m}),并且会到达状态 (dp_{i-1,j+1}),因此 (dp_{i,j}leftarrow dp_{i-1,j+1}·dfrac{n-j}{i+m})
      • 摸出一张数字牌,且在 (S) 中,概率 (dfrac{i+j-n}{i+m}),并且会到达状态 (dp_{i-1,j}),因此 (dp_{i,j}leftarrow dp_{i-1,j}·dfrac{i+j-n}{i+m})
      • 摸出一张鬼牌,概率 (dfrac{m}{i+m}),并且会到达状态 (dp_{n,j}),因此 (dp_{i,j}leftarrow dp_{n,j}·dfrac{m}{i+m})

      别忘了加上最后的 (1),因此对于 (j e 0) 的情况我们有转移方程 (dp_{i,j}=dp_{i-1,j+1}·dfrac{n-j}{i+m}+dp_{i-1,j}·dfrac{i+j-n}{i+m}+dp_{n,j}·dfrac{m}{i+m}+1)

    • 对于 (j=n)​ 的情况就比较 trivial 了,如果摸出一张数字牌那么会到达 (dp_{i-1,j})​,否则直接结束,因此 (dp_{i,n}=dp_{i-1,n}·dfrac{i}{i+m}+1)

    最终答案即为 (dp_{n,0})

    由于 DP 转移存在后效性,因此直接转移不可取,考虑高斯消元,直接高斯消元是六方的,不过注意到对于一个 (dp_{i,j}) 而言,如果我们倒着枚举 (j),那么我们就只用对 (j) 相同的这一行的值进行高斯消元,复杂度 (n^4)。还可以进一步优化,就是注意到在我们倒序枚举的过程中 (dp_{i-1,j+1}·dfrac{n-j}{i+m}) 是常数不用管它,如果我们不考虑这个 (dp_{n,j}) 那转移关系不成环就不存在后效性,而加上这个 (dp_{n,j}),由于导致这个后效性的只有 (dp_{n,j}),我们就可以考虑将所有 (dp_{i,j}) 都表示成 (sdp_{n,j}+t) 的形式,这样顺着一遍推过去,最后可以得到 (dp_{n,j}=sdp_{n,j}+t),解出 (dp_{n,j}) 后再推回去即可,这个套路可以在这道题中找到,时间复杂度 (n^2),反正还是过不去(

    附:(n^2) 的代码:

    const int MAXN=1000;
    const int MOD=998244353;
    int qpow(int x,int e){
    	int ret=1;
    	for(;e;e>>=1,x=1ll*x*x%MOD) if(e&1) ret=1ll*ret*x%MOD;
    	return ret;
    }
    int n,m,dp[MAXN+5][MAXN+5];
    int main(){
    //	freopen("toad.in","r",stdin);
    //	freopen("toad.out","w",stdout);
    	scanf("%d%d",&n,&m);
    	dp[0][n]=1;
    	for(int i=1;i<=n;i++) dp[i][n]=(1ll*i*qpow(i+m,MOD-2)%MOD*dp[i-1][n]+1)%MOD;
    	for(int j=n-1;~j;j--){
    		int cs=0,ct=0;
    		for(int i=n-j;i<=n;i++){
    			int coef1=1ll*m*qpow(i+m,MOD-2)%MOD;
    			int coef2=1ll*(i+j-n+MOD)*qpow(i+m,MOD-2)%MOD;
    			int coef3=1ll*(n-j)*qpow(i+m,MOD-2)%MOD;
    			cs=1ll*cs*coef2%MOD;ct=1ll*(ct+1)*coef2%MOD;
    			ct=(ct+1ll*(dp[i-1][j+1]+1)*coef3)%MOD;
    			cs=(cs+coef1)%MOD;ct=(ct+coef1)%MOD;
    		} dp[n][j]=1ll*ct*qpow((1-cs+MOD)%MOD,MOD-2)%MOD;//dp[n][j]=cs*dp[n][j]+ct
    		for(int i=n-j;i<n;i++){
    			int coef1=1ll*m*qpow(i+m,MOD-2)%MOD;
    			int coef2=1ll*(i+j-n+MOD)*qpow(i+m,MOD-2)%MOD;
    			int coef3=1ll*(n-j)*qpow(i+m,MOD-2)%MOD;
    			dp[i][j]=(1ll*(dp[i-1][j]+1)*coef2+1ll*(dp[i-1][j+1]+1)*coef3%MOD+1ll*(dp[n][j]+1)*coef1)%MOD;
    //			printf("%d %d %d
    ",i,j,dp[i][j]);
    		} //printf("%d %d %d
    ",n,j,dp[n][j]);
    	} printf("%d
    ",dp[n][0]);
    	return 0;
    }
    

    接下来考虑进一步优化。这里就要一些观察了,打个表可以发现,对于 (j) 相同的 (dp_{i,j}) 而言随着 (i) 的增大 (dp_{i,j})​ 成等差数列,换句话说所有 (dp_{i,j}) 都可以写成 (k_ji+b_j) 的形式。证明不会,大概可以归纳(?)(大概就发现 (dp_{i,n}) 是等差数列,而 (dp_{i,j}) 只从 (dp_{i,j+1}) 推来,这就天然地形成了归纳的模型,但具体怎么归纳我也没想出来)。这样对于每一个 (j),我们只用确定 (dp_{n,j})(dp_{n-1,j}),所有 (dp_{i,j}) 都确定了,方便起见这里假设 (dp_{i,j}=y_j(n-i)+x_j),这样我们可以列出这样两个方程组:

    [egin{cases} x_j=dp_{n-1,j+1}·dfrac{n-j}{n+m}+(x_j+y_j)·dfrac{j}{n+m}+x_j·dfrac{m}{n+m}+1\ x_j+y_j=dp_{n-2,j+1}·dfrac{n-j}{n+m-1}+(x_j+2y_j)·dfrac{j-1}{n+m-1}+x_j·dfrac{m}{n+m-1}+1 end{cases} ]

    (x_j,y_j) 解出来即可。

    时间复杂度 (nlog n)好像做不到 (mathcal O(n))

    const int MAXN=2e6;
    const int MOD=998244353;
    int qpow(int x,int e){
    	int ret=1;
    	for(;e;e>>=1,x=1ll*x*x%MOD) if(e&1) ret=1ll*ret*x%MOD;
    	return ret;
    }
    int n,m,x[MAXN+5],y[MAXN+5],f[MAXN+5];
    int calc(int i,int j){return (x[j]+1ll*(n-i)*y[j]%MOD)%MOD;}
    int main(){
    	scanf("%d%d",&n,&m);int ivn=qpow(n+m,MOD-2),ivn1=qpow(n-1+m,MOD-2);
    	f[0]=1;for(int i=1;i<=n;i++) f[i]=(1ll*i*qpow(i+m,MOD-2)%MOD*f[i-1]+1)%MOD;
    	x[n]=f[n];y[n]=(f[n-1]-f[n]+MOD)%MOD;
    	for(int j=n-1;j;j--){
    		int a1=1ll*(n-j)*ivn%MOD;
    		int b1=(MOD-1ll*j*ivn%MOD)%MOD;
    		int c1=1ll*(n-j)*ivn%MOD*calc(n-1,j+1)%MOD;
    		int a2=1ll*(n-j)*ivn1%MOD;
    		int b2=(1-1ll*(j-1+MOD)*2*ivn1%MOD+MOD)%MOD;
    		int c2=1ll*(n-j)*ivn1%MOD*calc(n-2,j+1)%MOD;
    		(c1+=1)%=MOD;(c2+=1)%=MOD;
    		y[j]=1ll*(1ll*c1*a2%MOD-1ll*c2*a1%MOD+MOD)*qpow((1ll*b1*a2%MOD-1ll*b2*a1%MOD+MOD)%MOD,MOD-2)%MOD;
    		x[j]=1ll*(c1-1ll*y[j]*b1%MOD+MOD)*qpow(a1,MOD-2)%MOD;
    	} printf("%d
    ",1ll*(1ll*calc(n-1,1)*n%MOD*ivn%MOD+1ll*m*ivn%MOD)*qpow(n,MOD-2)%MOD*(n+m)%MOD+1);
    	return 0;
    }
    
  • 相关阅读:
    [zz]利用__FILE__, __LINE__, __FUNCTION__跟踪调试程序
    [zz]va_start() 和 va_end()函数应用
    [zz]shmdt与shmctl的区别
    [zz]GNU C 扩展之__attribute__ 机制简介 [2]
    Linux errno 错误含义速查
    过滤器的简介
    MyBatis中的原理
    文件上传
    mybatis实体为什么要提供一个无参的构造函数
    为什么要有无参构造方法
  • 原文地址:https://www.cnblogs.com/ET2006/p/Codeforces-1392H.html
Copyright © 2011-2022 走看看