CF1392 H. ZS Shuffles Cards

题目叙述

n 张正常的牌(不同的)和 m 张鬼牌,每次可以抽出一张牌,如果是鬼牌则将所有抽到的牌都放回去。如果是正常的牌则收下。问期望多少次能够将所有的正常牌都抽一遍。

题解

方法一

fi,j 表示现在抽到过 i 张正常的牌,桌子上有 j 张牌和手里的重合,期望还需要多少次。
转移是容易的:

fi,j=jni+j+mfi,j1+nini+j+mfi+1,j+mni+j+mfi,i+1

考虑差分。fi,jfi+1,j 会得到一个算式。然后可以发现这是一个关于他们差分的递推式。
而我们发现 f0i 构成一个等差数列。根据这个关于 fi,j 的递推式,可以发现 fi,jfi,j+1 的差也是一个固定的数,而且就是 1m+1
就此就可以通过 fi,0 推出 fi,i 的值。
再根据 fi,j 的递推式算出 fi1,0fi,0 的递推,就可以算出答案了。

方法二

可以发现两次抽出鬼牌之间的期望次数是相等的,都是 n+mm 次。
所以其实只需要求出最后一次抽出的鬼牌是第几张抽出的即可。
fi 表示第 i 张牌抽出之后第一张不是鬼牌的牌是抽出的第几次抽出鬼牌。
所以我们要求的就是 E(maxfi)
考虑 minmax 容斥,E(maxfi)=sU(1)|s|+1E(minjsfj)
其中 U 为所有牌组成的集合。
E(minisfi) 的含义就是在抽出这一张牌之前,抽出的鬼牌数量+1。
可以发现这时候抽出一张需要的牌的概率是固定的,即 |s||s|+m ,因此期望步数就是 |s|+m|s| 。再套上那个算式计算一下就好了,具体就不细说了。

总结

  • 可以先猜测一些二维递推比较简单之类的。这个就每行都是一个等差数列。
  • 另外对于这种递推式可以尝试差分一下。会有一些效果。
  • 如果达成一件事情的概率在变化(比如这题里因为牌数变化的原因导致的问题),那么可以考虑 minmax 容斥把变化的转化为不变的。

代码

#include <cstdio>
#include <iostream>
#define macro_expand(x) #x
#define print_macro(x) printf("%s\n",macro_expand(x))
#define FOR(i,l,r) for(int i=(l);i<=(r);++i)
#define ROF(i,r,l) for(int i=(r);i>=(l);--i)
using namespace std;
typedef long long LL;
const int Mod=998244353;
int add(int &x,int y){return ((x+=y)>=Mod)?(x-=Mod):x;}
int dec(int &x,int y){return ((x-=y)<0)?(x+=Mod):x;}
int ad(int x,int y){return ((x+y)>=Mod)?(x+y-Mod):(x+y);}
int dc(int x,int y){return ((x-y)<0)?(x-y+Mod):(x-y);}
int ml(int x,int y){return (LL)x*y%Mod;}
int ksm(int x,int y){
	int ret=1;
	for(;y;y>>=1,x=ml(x,x))if(y&1)ret=ml(ret,x);
	return ret;
}
int N,M;
int main(){
	// freopen("h.in","r",stdin);
	// freopen(".out","w",stdout);
	scanf("%d%d",&N,&M);
	int ans=0;
	for(int i=0;i<=N-1;++i){
		int tmp=ad(ml(M,N),ad(ml(M,M),ad(N-i,M)));
		add(ans,ml(tmp,ksm(ml(M+1,N-i),Mod-2)));
	}
	printf("%d\n",ad(ans,1));
	// fclose(stdin);
	// fclose(stdout);
	return 0;
}
posted @   YouthRhythm  阅读(11)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 分享一个免费、快速、无限量使用的满血 DeepSeek R1 模型,支持深度思考和联网搜索!
· 基于 Docker 搭建 FRP 内网穿透开源项目(很简单哒)
· ollama系列01:轻松3步本地部署deepseek,普通电脑可用
· 25岁的心里话
· 按钮权限的设计及实现
点击右上角即可分享
微信分享提示