CCPC2020 威海 E.So Many Possibilities...

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 刀有两种情况:

  1. 砍死一只新怪物,设其为 j 。那么 (i-sum[S]) 中应该有 (a_j-1) 次操作砍在 j 上。贡献是 ({i-sum[S] choose a_j-1} / |ar S|)
  2. 没有砍死。那么当前贡献是 (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

*/

原文地址:https://www.cnblogs.com/dqsssss/p/13882193.html