多校7 HDU5816 Hearthstone 状压DP+全排列

  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 }

 

转载于:https://www.cnblogs.com/ITUPC/p/5764612.html

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值