比赛的时候没想出来..
题意:给p,n,m三个数,分别表示敌人的血量,可抽A牌的数量(A牌可以再抽两张),可抽B牌的数量(B牌对敌人造成a[i]点伤害),问现在轮到我抽一张牌(抽了A牌后又可以抽2张,如此往复),问最后打死敌人的概率。
样例:
第一组:5/15(只能从15张牌里先取1张A牌才能获胜)
第二组:[(5/15) * (10/14) * (4/13)] + [(5/15) * (4/14) * (10/13)] + [(5/15) * (4/14) * (3/13)]
思路:上面样例是直观计算的,用程序不太好跑,仔细想一下:
其实就是问:(打死敌人的方案数 / 总的取牌方案数),n+m<=20可以考虑用状态压缩枚举出所有取牌的情况,看了别人的博客,这题只要求出一个伤害h>=p当前状态就可以不向下递推了,可以用N-A-B(这里代表取牌的数量)求全排列。
#include <iostream>
#include <algorithm>
#include <cstdio>
#include <cstring>
using namespace std;
typedef long long ll;
ll dp[1 << 21];
ll f[21];
int a[21];
void init(){
f[0] = 1;
for(int i = 1;i<=20;i++) f[i] = f[i-1] * (i * 1LL);
}
int main()
{
init();
int t;
scanf("%d",&t);
while(t--){
int n,m,p;
scanf("%d%d%d",&p,&n,&m);
for(int i =0;i<m;i++)
scanf("%d",&a[i]);
int N = n + m;//[0,m)代表B卡的数量,[m,N)表示A卡数量
memset(dp,0,sizeof dp);dp[0] = 1;
for(int s = 0;s< (1 << N);s++){
if(dp[s]!=0){
int A =0,B =0,h=0;//A,B牌数和伤害点数
for(int i = 0;i< m;i++){
if((s >> i )& 1){
B++;h+=a[i];
}
}
if(h>=p) continue;//已经可以打死对手,不需要拿牌了
for(int i = m;i<N;i++)
if((s>>i)&1) A++;
if(A-B +1>0){
for(int i =0;i<N;i++)
if(!(s>>i&1)) dp[s^(1<<i)]+=dp[s];
}
}
}
ll up = 0,down = f[N];
for(int s = 0;s<(1<<N);s++){
if(dp[s]!=0){
int A =0,B =0,h=0;
//枚举当前拿牌方案中A,B牌的数量
for(int i = 0;i<m;i++)
if(s >> i & 1) {B++;h+=a[i];}
for(int i = m;i<N;i++)
if(s >> i & 1) A++;
if(h>=p){
up+=dp[s] * f[N-A-B];
}
}
}
ll g = __gcd(up,down);
printf("%I64d/%I64d\n",up/g,down/g);
}
return 0;
}