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

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

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

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

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

  • 对于 \(j\ne 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\ne 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\n",i,j,dp[i][j]);
} //printf("%d %d %d\n",n,j,dp[n][j]);
} printf("%d\n",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\),这样我们可以列出这样两个方程组:

\[\begin{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\) 解出来即可。

时间复杂度 \(n\log 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\n",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;
}

最新文章

  1. InnoDB还是MyISAM 再谈MySQL存储引擎的选择
  2. 浅谈iOS开发中方法延迟执行的几种方式
  3. Android之alertDialog、ProgressDialog
  4. java学习第5天
  5. 3.openssl speed和openssl rand
  6. ES5 数组方法map
  7. CSS基础选择器温故-1
  8. ci实现RBAC,详细解释原理和核心代码显示
  9. hdu-----(3746)Cyclic Nacklace(kmp)
  10. Sticks(poj1011/uva307)
  11. poj 3150 Cellular Automaton
  12. 跟着大神重写的KNN 文档归类小工具
  13. spring的value,null标签
  14. 2017-03-02学习心得之Java代码
  15. UWP 拖拽文件
  16. 【转载】Python中的垃圾回收机制
  17. kindEditor 富文本编辑器 使用介绍
  18. Dockfile基本语法
  19. java反射出字段信息和值
  20. 浅谈Python闭包

热门文章

  1. 6月4日 Scrum Meeting
  2. 所驼门王的宝藏(Tarjan)
  3. C语言中都有哪些常见的数据结构你都知道几个?
  4. 云效Flow如何实现阿里云ECS多环境发布
  5. A*,IDA*—高档次的暴搜
  6. Netty:Reactor Pattern 与 Dubbo 底层传输中的 NettyServer
  7. fd定时器--timerfd学习
  8. js 在浏览器中的event loop事件队列
  9. ELK 脚本自动化删除索引
  10. 几十行js实现很炫的canvas交互特效