HDU 5816 Hearthstone(状压DP)

40 篇文章 1 订阅

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);
    }
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值