hdu5816 卡特兰数+dp

题意:共n张无中生有,m张攻击牌。每张攻击牌攻击力已知,敌方有p点血。随机洗牌。游戏开始,己方抽取一张手牌,若是无中生有则可再抽两张牌。求能在第一回合内将敌方杀死的概率。

n+m <= 20, p <= 1000;

很明显,与卡特兰数有关,原先栈内数量为1,抽到无中生有即入栈,否则出栈。

枚举攻击牌,求出该攻击牌组合下,用完所有手牌将对方杀死的方案数,以及抽光所有牌将对方杀死的方案数(手牌有剩)。

不算预处理的复杂度,每组数据的时间复杂度为O(2^m)

 1 #include <cstdio>
 2 typedef long long ll;
 3 
 4 int bc[1<<20], sum[1<<20], tmp[1<<20];
 5 int C[21][21];
 6 ll fact[21];
 7 
 8 ll gcd(ll a, ll b){
 9     return b == 0? a :gcd(b, a%b);
10 }
11 
12 void init(){
13     int i, j;
14     bc[0] = 0;
15     for (i=1; i<(1<<20); i++) bc[i] = bc[i^(i&-i)] + 1;
16     fact[0] = 1;
17     for (i=1; i<=20; i++) fact[i] = fact[i-1] * i;
18     C[0][0] = 1;
19     for (i=1; i<=20; i++){
20         C[i][0] = C[i][i] = 1;
21         for (j=1; j<i; j++) C[i][j] = C[i-1][j] + C[i-1][j-1];
22     }
23 }
24 
25 int main(){
26     ll a, b, d;
27     int p, n, m, t, i;
28     init();
29     scanf("%d", &t);
30     while (t --){
31         scanf("%d %d %d", &p, &n, &m);
32         for (i=0; i<m; i++) scanf("%d", &tmp[1<<i]);
33         sum[0] = 0;
34         for (i=1; i<(1<<m); i++) sum[i] = sum[i^(i&-i)] + tmp[i&-i];
35         a = 0;
36         for (i=0; i<(1<<m); i++){
37             if (sum[i] >= p && bc[i] <= n + 1){
38                 //C(n + m − 1,m) − C(n + m − 1,m − 1)
39                 //手牌用完,到(bc[i], bc[i])的方案数,即到(bc[i], bc[i]-1)的方案数, 需要bc[i]-1张无中生有
40                 a += C[n][bc[i]-1] * (C[ bc[i]+bc[i]-2 ][ bc[i]-1 ] - C[ bc[i]+bc[i]-2 ][ bc[i]-2 ])* fact[bc[i]-1] * fact[bc[i]] * fact[n+m-2*bc[i]+1];
41                 //手牌用不完, 到(n+1, m)的方案数
42                 if(bc[i] == m&&bc[i] < n+1)
43                     a += (C[ m+n ][ m ] - C[ m+n ][ m-1 ])* fact[n] * fact[m];
44             }
45         }
46         b = fact[n+m];
47         d = gcd(a, b);
48         printf("%I64d/%I64d
", a / d, b / d);
49     }
50     return 0;
51 }
View Code

 当时写的时候是以 状态表示的牌将敌方杀死 作为结束点,似乎还要容斥,比如用1,2,3杀死对方和用1,2就杀死对方,复杂度也可能会爆炸;

其实应该换一个角度,考虑手牌用光将对方杀死,再加上手牌用不光的case,终点已知,那么就不会有容斥关系。

原文地址:https://www.cnblogs.com/dirge/p/5755177.html