1 多校7 HDU5816 Hearthstone 状压DP+全排列 2 题意:boss的PH为p,n张A牌,m张B牌。抽取一张牌,能胜利的概率是多少? 3 如果抽到的是A牌,当剩余牌的数目不少于2张,再从剩余牌里抽两张,否则全部拿完。 4 每次拿到一张B牌,对boss伤害B[i]的值 5 思路:dp[i]表示状态为i时的方案数 6 先处理出所有状态下的方案,再枚举每种状态,如果符合ans+=dp[i]*剩余数的全排列 7 当前集合里有a张A,b张B,那么还能取的牌数:a*2-a-b+1 8 9 #include <bits/stdc++.h> 10 using namespace std; 11 #define LL long long 12 const int inf = 0x3f3f3f3f; 13 const int MOD =998244353; 14 const int N =200010; 15 #define clc(a,b) memset(a,b,sizeof(a)) 16 const double eps = 1e-7; 17 void fre() {freopen("in.txt","r",stdin);} 18 void freout() {freopen("out.txt","w",stdout);} 19 inline int read() {int x=0,f=1;char ch=getchar();while(ch>'9'||ch<'0') {if(ch=='-') f=-1;ch=getchar();}while(ch>='0'&&ch<='9') {x=x*10+ch-'0';ch=getchar();}return x*f;} 20 int n,m,p; 21 LL dp[1<<21]; 22 int B[21]; 23 LL fac[21]={1}; 24 int main(){ 25 fac[0]=1; 26 for(int i=1;i<21;i++) fac[i]=fac[i-1]*i; 27 int T,sum,a,b; 28 scanf("%d",&T); 29 while(T--){ 30 scanf("%d%d%d",&p,&n,&m); 31 int N=n+m; 32 for(int i=0;i<1<<N;i++) dp[i]=0; 33 for(int i=n;i<N;i++) scanf("%d",&B[i]); 34 35 dp[0]=1,sum=0,b=0,a=0; 36 for(int i=0;i<1<<N;i++){ 37 if(dp[i]==0) continue; 38 sum=0,a=0,b=0; 39 for(int j=n;j<N;j++){ 40 if(i&(1<<j)){ 41 sum+=B[j]; 42 b++; 43 } 44 } 45 if(sum>=p)continue; 46 for(int j=0;j<n;j++){ 47 if(i&(1<<j)){ 48 a++; 49 } 50 } 51 if(a-b+1<=0) continue; 52 for(int j=0;j<N;j++){ 53 if((i&(1<<j))==0){ 54 dp[i|(1<<j)]+=dp[i]; 55 } 56 } 57 } 58 LL Den=fac[N],Mol=0; 59 for(int i=0;i<(1<<N);i++){ 60 a=0,b=0,sum=0; 61 for(int j=n;j<N;j++){ 62 if(i&(1<<j)){ 63 b++;sum+=B[j]; 64 } 65 } 66 if(sum<p) continue; 67 for(int j=0;j<n;j++){ 68 if(i&(1<<j)){ 69 a++; 70 } 71 } 72 Mol+=(LL)dp[i]*fac[N-b-a]; 73 } 74 LL g=__gcd(Mol,Den); 75 printf("%I64d/%I64d ",Mol/g,Den/g); 76 } 77 return 0; 78 } 79 80 81 82 解法2标程:O(2^m) 83 f[i][j]表示A拿i张,B拿j张的方案数 84 方法用f[i][j]表示A类牌和B类牌分别抽到i张和j张,且抽牌结束前保证i>=j的方案数,这个数组可以用O(n^2)的dp预处理得到. 85 接下来枚举B类牌的每个子集,如果这个子集之和不小于P,用k表示子集的1的个数,将方案总数加上取到这个集合刚好A类卡片比B类卡片少一(过程结束)的方案数:f[k-1][k] * C(n, k - 1) * (k - 1)! * k! * (n + m – 2*k + 1)! . 86 如果子集包含了所有的B类卡片,则还需要再加上另一类取牌结束的情况,也就是取完所有牌,此时应加上的方案数为f[n][m] * n! * m! . 87 最后的总方案数除以(n+m)!就是答案. 88 89 90 #include <cstdio> 91 typedef long long lli; 92 93 int bc[1<<20], sum[1<<20], tmp[1<<20]; 94 int C[21][21]; 95 lli f[21][21], fact[21]; 96 97 lli gcd(lli a, lli b) 98 { 99 if (b == 0) return a; 100 return gcd(b, a % b); 101 } 102 103 void init() 104 { 105 int i, j; 106 bc[0] = 0; 107 for (i=1; i<(1<<20); i++) bc[i] = bc[i^(i&-i)] + 1; 108 fact[0] = 1; 109 for (i=1; i<=20; i++) fact[i] = fact[i-1] * i; 110 C[0][0] = 1; 111 for (i=1; i<=20; i++) 112 { 113 C[i][0] = C[i][i] = 1; 114 for (j=1; j<i; j++) C[i][j] = C[i-1][j] + C[i-1][j-1]; 115 } 116 f[0][0] = f[0][1] = 1; 117 for (i=1; i<=20; i++) 118 { 119 f[i][0] = 1; 120 for (j=1; j<i; j++) f[i][j] = f[i-1][j] + f[i][j-1]; 121 f[i][i] = f[i][i+1] = f[i][i-1]; 122 } 123 } 124 125 int main() 126 { 127 lli a, b, d; 128 int p, n, m, t, i; 129 init(); 130 scanf("%d", &t); 131 while (t --) 132 { 133 scanf("%d %d %d", &p, &n, &m); 134 for (i=0; i<m; i++) scanf("%d", &tmp[1<<i]); 135 sum[0] = 0; 136 for (i=1; i<(1<<m); i++) sum[i] = sum[i^(i&-i)] + tmp[i&-i]; 137 a = 0; 138 for (i=0; i<(1<<m); i++) 139 { 140 if (sum[i] >= p && bc[i] <= n + 1) 141 { 142 a += C[n][bc[i]-1] * f[bc[i]-1][bc[i]] * fact[bc[i]-1] * fact[bc[i]] * fact[n+m-2*bc[i]+1]; 143 if (bc[i] == m && bc[i] < n + 1) a += f[n][m] * fact[n] * fact[m]; 144 } 145 } 146 b = fact[n+m]; 147 d = gcd(a, b); 148 printf("%I64d/%I64d ", a / d, b / d); 149 } 150 return 0; 151 }