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\n",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\n", a / d, b / d);
149 }
150 return 0;
151 }