2020 CCPC Weihai E. So Many Possibilities... 题解 feat. minstdfx
StеlІаwіnD · · 个人记录
题意:有
我一开始想把所有可能的操作序列拿出来然后怎么容斥着计算一下,然后看样例就发现它是错的,因为不同的操作序列出现的概率不同。
接下来我想要设计一个 dp,
然后我去看了题解,发现了一个重要的观察:我并不需要保证过程中不击杀其他的随从。我设一个操作序列
这样我就不需要推 dp 的时候考虑会不会误杀其他的随从了,因为可以在计算答案的时候,对于某一个状态对于所有
记
接下来就应该推转移了。但是我现在只知道如果下一步会击杀新的随从,转移式里会有一个组合数(从当前序列里所有的
显然我们有
我们分两种情况讨论转移:
- 最后一次操作干掉了一个
u\in S 。
那么最后一次打到u 的概率是\frac 1 {n-|S|+1} 。 在前m-1 次操作中,有\sum_{i\in S,i\neq u} a_i 次打中其他随从,有a_u-1 次打中u ,其余都是打中别的,也就是0 。挑出m-1-\sum_{i\in S,i\neq u} a_i 个位置中的a_i-1 个给u ,那么就是 - 最后一次操作是未命中,也就是
0
这个简单啊,就是dp_{S,m-1}/(n-|S|) 。
那么如何计算答案呢?
我们假设最终死掉的集合为
其中
如何计算
:::
我看到这个的时候,我产生了一个问题:为什么要在转移 dp 的时候除以选择数呢?同时 fx 给到的 g 数组的定义也和我前面定义的不同,是方案数而非概率。我猜想这是因为“不同的操作序列出现的概率不同”这个问题仍然没被解决(虽然我还没完全明白为什么这样转移就可以算出正确的概率)。
:::error[补充2]
其实一开始我看到你有疑问我很好奇。主要是当时我写这个的时候你可能自己定义了
接下来我相信不需要解释为什么要在转移 dp 的时候除以选择数了。
在做转移时,考察 dp 的定义,我们只需要把这种等概率选人的概率因子乘进去;而非致死的攻击到底分给了谁以及顺序怎么排的组合个数交给 g 和组合数去统计。
所以在 dp 里每插入一步操作,就必须除以当时可被选中的人数。
:::
总时间复杂度
这个题有一个坑,就是有可能
int a[20],sum[40000];
long double F[110][80000],G[110][80000],C[110][110];
void solve(){
int n,m;read(n),read(m);
rep(i,0,n-1)read(a[i]);
rep(i,0,(1<<n)-1)rep(j,0,n-1)if((i>>j)&1)sum[i]+=a[j];
rep(i,0,m)C[i][0]=C[i][i]=1;
rep(i,1,m)rep(j,1,i-1)C[i][j]=C[i-1][j]+C[i-1][j-1];
F[0][0]=1e300;
rep(i,1,m){
rep(j,0,(1<<n)-1){
int S=__builtin_popcount(j);
if(n!=S)F[i][j]+=F[i-1][j]/(n-S);
rep(k,0,n-1)if((j>>k)&1){
int R=i-sum[j];if(R<0)continue;
F[i][j]+=F[i-1][j^(1<<k)]*C[i-sum[j^(1<<k)]-1][a[k]-1]/(n-S+1);
}
}
}
G[0][0]=1;
rep(i,0,m)rep(j,1,(1<<n)-1){
int X=__builtin_ctz(j);
rep(k,0,a[X]-1)if(i>=k)G[i][j]+=C[i][k]*G[i-k][j^(1<<X)];
}
long double ans=0;
rep(i,0,(1<<n)-1){
if(sum[i]<=m)ans+=__builtin_popcount(i)*F[m][i]*G[m-sum[i]][(1<<n)-1-i];
}
printf("%.12Lf\n",ans/F[0][0]);
}