CCPC2020 威海 E.So Many Possibilities...
E
题意:n 个怪物,第 i 个怪物有血量 (a_i) 。 m 次操作,每次从还活着(血量>0)的怪物中等概率随机挑选一个使其血量减一,问 m 次操作后期望杀死多少只怪物。 (n le 15, m,a_i le 100, sum a_i > m)
key:概率 dp
如果知道哪些死了,也知道操作数,那么没有针对最终死亡的怪物的操作次数也是可以知道的,并且实际上针对谁并不重要。也就是说,对于一个操作序列,我们只需要知道哪些操作是针对最终死亡的即可,剩下的可以直接乘一个系数。
(g[i][S]) 表示对于 (j in S) 的所有怪物一共砍 i 刀且都不砍死的方案数。等价于 i 个石头染色,第 (j) 种颜色小于等于 (a_j-1) 块石头的方案数。暴力枚举 S 然后 dp 是 (O(2^nnm^2)) ,边 dfs 边 dp 就变成 (O(2^nm^2)) 。这个就是那个系数,即活着的怪物的贡献。
现在考虑死掉的怪物的贡献。记 (S) 表示生死状态,0 为生, 1 为死。 (f[i][S]) 表示砍 i 刀使得生死状态为 S 的概率,(sum[S]) 表示 (jin S) 的所有怪物血量之和,那么其中 (i-sum[S]) 次操作是砍在活着的怪物上的,在这里贡献是 1 (因为当前只考了死掉的怪物)。那么第 i+1 刀有两种情况:
- 砍死一只新怪物,设其为 j 。那么 (i-sum[S]) 中应该有 (a_j-1) 次操作砍在 j 上。贡献是 ({i-sum[S] choose a_j-1} / |ar S|) 。
- 没有砍死。那么当前贡献是 (1/|ar S|) 。
要除以 (|ar S|) 是因为第 i+1 次操作的随机性。复杂度是 (O(2^nnm)) 。
最终答案是 (sum_{S} f[m][S]*g[m-sum[S]][ar S]*|S|) 。
于是总复杂度是 (O(2^n(n+m)m)) 。
#include<bits/stdc++.h>
using namespace std;
typedef long long LL;
typedef long double LD;
typedef pair<LL,LL> pii;
typedef pair<double, double> pdd;
const int SZ = 50100;
const double INF = 1e10 + 10;
const int mod = 998244353;
const LD eps = 1e-8;
LL read() {
LL n = 0;
char a = getchar();
bool flag = 0;
while(a > '9' || a < '0') { if(a == '-') flag = 1; a = getchar(); }
while(a <= '9' && a >= '0') { n = n * 10 + a - '0',a = getchar(); }
if(flag) n = -n;
return n;
}
int n,m,a[117];
double f[117][SZ],C[110][110];
double g[117][110];
double G[110][SZ];
int sum[SZ];
int NT[SZ];
// 0:live 1:dead
void print(int x) {
for(int i = 0;i < n;i ++)
printf("%d",x>>i&1);
printf("");
}
void dfs(int d, int S, int nt) {
if(d == n) {
for(int i = 0;i <= m;i ++) {
G[i][S] = g[nt][i];
}
return ;
}
for(int i = 0;i <= m;i ++) {
for(int j = 0;j < a[d+1] && j <= i;j ++)
g[nt+1][i] += g[nt][i-j] * C[i][j];
}
dfs(d+1,S,nt+1); // d is live
for(int i = 0;i <= m;i ++) {
g[nt+1][i] = 0;
}
dfs(d+1,S^(1<<d),nt);
}
void init() {
C[0][0] = 1;
for(int i = 1;i <= 100;i ++) {
C[i][0] = 1;
for(int j = 1;j <= i;j ++) {
C[i][j] = C[i-1][j]+C[i-1][j-1];
}
}
}
bool isok(int i,int S) {
int nS = (1<<n)-S-1;
return i >= sum[S] && sum[nS] - NT[S] >= i-sum[S];
}
int main() {
init();
n = read(),m = read();
for(int i = 1;i <= n;i ++) a[i] = read();
for(int S = 0;S < (1<<n);S ++) {
for(int i = 0;i < n;i ++) {
if(S>>i&1)
sum[S] += a[i+1];
}
}
g[0][0] = 1;
dfs(0,0,0);
for(int S = 0; S < (1<<n);S ++) { // S,t: dead ns,nt: live
int nt = 0;
for(int i = 0;i < n;i ++) if(!(S>>i&1)) nt ++;
NT[S] = nt;
}
// 0:live 1:dead
double ans = 0;
f[0][0] = 1;
for(int S = 0; S < (1<<n);S ++) { // 1,t: dead 0,nt: live
int nt = 0;
for(int i = 0;i < n;i ++) if(!(S>>i&1)) nt ++;
int t = n - nt;
int nS = (1<<n)-S-1;
for(int i = 0;i < m;i ++) {
if(isok(i,S)) {
double x = 1;
for(int j = 0;j < n;j ++) {
if(!(S>>j&1)) { // j is alive, make it dead
if(isok(i+1,S^(1<<j))) {
double k = C[i-sum[S]][a[j+1]-1] / nt;
f[i+1][S^(1<<j)] += f[i][S] * k;
}
}
}
if(isok(i+1,S)) {
f[i+1][S] += f[i][S] / nt;
}
} else {
f[i][S] = 0;
}
}
if(m >= sum[S]) {
ans += f[m][S] * G[m-sum[S]][S] * t;
}
}
printf("%.10f
",(double)ans);
}
/*
15 100
7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
*/