题意:共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 }
当时写的时候是以 状态表示的牌将敌方杀死 作为结束点,似乎还要容斥,比如用1,2,3杀死对方和用1,2就杀死对方,复杂度也可能会爆炸;
其实应该换一个角度,考虑手牌用光将对方杀死,再加上手牌用不光的case,终点已知,那么就不会有容斥关系。