您的位置:首页 > 其它

HDU 5816 Hearthstone(状压DP)

2016-08-30 19:36 309 查看
Description

有一个卡组,有两种卡牌,A牌n张,B牌m张,抽到一张A牌可以从卡组中再抽两张,抽到一张B牌可以给敌人造成一定的伤害,现在告诉敌人的血量P以及m张B牌的伤害值,初始状态可以从卡组中拿一张牌,问有多大概率可以打败敌人(即抽到牌的总伤害值大于等于敌人的血量)

Input

第一行一整数T表示用例组数,每组用例首先输入三个整数p,n,m分别表示敌人血量,A牌数,B牌数,之后m个整数ai表示第i张B牌的伤害值

(T<=10,p<=1000,n+m<=20,0< ai<=1000)

Output

对于每组用例,输出打败敌人的概率(输出最简分数形式)

Sample Input

2

3 1 2

1 2

3 5 10

1 1 1 1 1 1 1 1 1 1

Sample Output

1/3

46/273

Solution

因为最多20张牌,所以想到用一个20位二进制数表示一个状态,用dp[i]表示拿到牌的状态为i是否合法,初始化就是dp[1<< i]=1,i=0,1,…,n+m-1,对于一个状态i,如果已经拿到x张A牌和y张B牌,统计这y张B牌的总伤害,如果大于等于p则后面的牌随意排列都可以打败敌人,那么对答案的贡献就是dp[i]*(n+m-x-y)!,之后通过判断这个状态是否可以继续拿牌来转移到下一个状态,x张A牌可以拿2x张牌,去掉初始手中的一张牌,故若2x-x-y+1>0说明该状态可以接着拿牌,进而枚举这个状态中0的位赋为1即转移到一下状态

Code

#include<cstdio>
#include<iostream>
#include<cstring>
#include<algorithm>
using namespace std;
typedef long long ll;
#define maxn ((1<<20)+11)
ll f[maxn];
int cnt[maxn];
ll gcd(ll a,ll b)
{
return b?gcd(b,a%b):a;
}
void init()
{
f[0]=1;
for(int i=1;i<=20;i++)f[i]=1ll*i*f[i-1];
for(int i=0;i<(1<<20);i++)
{
cnt[i]=0;
for(int j=0;j<20;j++)
if(i&(1<<j))cnt[i]++;
}
}
int T,p,n,m,v[22];
ll dp[maxn];
bool check(int x)
{
int ans=0;
for(int i=n;i<n+m;i++)
if(x&(1<<i))ans+=v[i-n];
if(ans>=p)return 1;
return 0;
}
int main()
{
init();
scanf("%d",&T);
while(T--)
{
scanf("%d%d%d",&p,&n,&m);
for(int i=0;i<m;i++)scanf("%d",&v[i]);
memset(dp,0,sizeof(dp));
for(int i=0;i<n+m;i++)dp[1<<i]=1;
ll ans=0,N=1<<(n+m);
for(int i=0;i<N;i++)
{
int tot=cnt[i],x=cnt[i&((1<<n)-1)],y=tot-x;
if(check(i))
{
ans+=dp[i]*f[n+m-tot];
continue;
}
if(x-y+1<=0)continue;
for(int j=0;j<n+m;j++)
if(!(i&(1<<j)))dp[i|(1<<j)]+=dp[i];
}
ll g=gcd(ans,f[n+m]);
printf("%I64d/%I64d\n",ans/g,f[n+m]/g);
}
}
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: