题意:从包中等概率抽取一个卡片,卡片上面有一个数字。不放回抽取,然后从第二次抽取开始,假设当前抽到的数字是x,上一次抽到的数字是y。那么:
- if x<y, the game ends and you lose;
- if x=y, the game ends and you win;
- if x>y, the game continues.
然后求win的概率。
分析:考虑每个数字的贡献,只有个数大于等于2时才会有贡献。枚举这个数字为最后抽取的数字,那么在之前抽取到的数字只会比这个小,而且严格递增有序,假设当前数字为x,那么相当于是从前x-1个数字里面抽不重复的若干个,这个可以看作是一个背包。d[i][j]
表示从前 i 个里面抽取 j 个的方案数是多少个。然后抽取 当前结尾数字x时,设x的个数是num[x]个,那么根据排列组合的知识可以知道方案数是num[x] * (num[x]-1)
,然后我们现在共抽取了2+j 个卡片,赢得了比赛,但是计算总概率时,我们的计算方法是:(赢得的方案总方案数赢得的方案 div 总方案数),因为总方案是 n!,相当于把 n 个卡片每一个的可能都列了进去,所以当我们抽取了 2+j 个卡片赢得了比赛时,我们不能忽略后面 n-2-j
个卡片的顺序。
综上,数字x的贡献是
求出每个数字的贡献,就得到了赢得的方案数,然后除以(n!) 即可。注意所有的操作都是在取模操作下,所以除法运算要计算逆元。
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N = 5005;
const int mod = 998244353;
int n,a[N];
ll num[N],fac[N],dp[N][N];
ll quick_mod(ll a,ll b){
ll res = 1;
for(;b;b>>=1){
if(b&1)res = res * a % mod;
a = a * a % mod;
}
return res;
}
int main(){
scanf("%d",&n);
for(int i=1;i<=n;i++){
scanf("%d",&a[i]);
num[a[i]]++;
}
fac[0] = 1;
for(int i=1;i<=5004;i++)fac[i] = fac[i-1] * i % mod;
dp[0][0] = 1;
for(int i=1;i<=n;i++){
dp[i][0] = 1;
for(int j=1;j<=i;j++){
dp[i][j] = (dp[i-1][j] + dp[i-1][j-1] * num[i])%mod;
}
}
ll res = 0;
for(int i=1;i<=n;i++){
if(num[i] >= 2){
for(int j=i-1;j >= 0;j--){
res = (res + dp[i-1][j] * num[i] %mod * (num[i] - 1) %mod * fac[n-j-2] %mod )%mod;
}
}
}
printf("%lld
",res * quick_mod(fac[n],mod-2)%mod);
return 0;
}
官方题解方法
d[i][j]
表示抽取了 j 个卡片,最后抽取的是数字为 i 的卡片时可以赢得的概率。如何计算d[i][j]
当然是把从 [i][j]
状态转移出去的状态(具体转移到哪个是等概率的)的概率求和就可以了。
- 第一种转移显然是继续拿一个 i 。然后这种方案赢得的概率是 ({cnt_i-1 over n-j})
- 第二种是拿数字大于 i 的(注意,一次转移只能拿一个)。所以这种方案赢得的概率是(Sigma_{k=i+1}^n{({cnt_k over n-j} * d[k][j+1])})
所以(d[i][j] = {1over n-j} * ((cnt_i-1) + Sigma_{k=i+1}^n(cnt_k * d[k][j+1])))
所以从后往前遍历,用一个数组维护(Sigma_{i=x}^n(cnt_i*d[i][j])) 即可
温馨提示:公式很好懂,但是逆元你真的会用了吗?
#include<bits/stdc++.h>
using namespace std;
const int MOD = 998244353;
const int N = 5005;
//注意a是引用
void upd(int &a, int b){
a += b;
a %= MOD;
}
//防止爆int,然后又不用ll,所以用这个
int mul(int a, int b){
return (a * 1LL * b) % MOD;
}
//快速幂
int bp(int a, int n){
int res = 1;
for(; n > 0; n >>= 1){
if(n & 1) res = mul(res, a);
a = mul(a, a);
}
return res;
}
int getInv(int a){
int ia = bp(a, MOD - 2);
assert(mul(a, ia) == 1);
return ia;
}
int n;
int cnt[N];
int dp[N][N];
int sum[N][N];
int inv[N];//存逆元
int main(){
for(int i = 1; i < N; ++i)
inv[i] = getInv(i);
cin >> n;
for(int i = 0; i < n; ++i){
int x;
cin >> x;
++cnt[x];
}
cnt[0] = 1;
//从后往前
for(int x = n; x >= 0; --x)
for(int y = n; y >= 0; --y){
if(cnt[x] == 0){
upd(sum[x][y], sum[x + 1][y]);
continue;
}
int s = n - y;
if(s <= 0){
upd(sum[x][y], sum[x + 1][y]);
continue;
}
upd(dp[x][y], mul(cnt[x] - 1, inv[s]));//加上贡献的第一项
upd(dp[x][y], mul(sum[x + 1][y + 1], inv[s]));//第二项
//维护sum[x][y]
upd(sum[x][y], sum[x + 1][y]);
upd(sum[x][y], mul(cnt[x], dp[x][y]));
}
cout << dp[0][0] << endl;
return 0;
}