码迷,mamicode.com
首页 > 其他好文 > 详细

HDU 5816 Hearthstone

时间:2016-08-09 19:08:39      阅读:285      评论:0      收藏:0      [点我收藏+]

标签:

Time Limit: 2000/1000 MS (Java/Others)    Memory Limit: 65536/65536 K (Java/Others)


Problem Description
Hearthstone is an online collectible card game from Blizzard Entertainment. Strategies and luck are the most important factors in this game. When you suffer a desperate situation and your only hope depends on the top of the card deck, and you draw the only card to solve this dilemma. We call this "Shen Chou Gou" in Chinese.
技术分享
Now you are asked to calculate the probability to become a "Shen Chou Gou" to kill your enemy in this turn. To simplify this problem, we assume that there are only two kinds of cards, and you don‘t need to consider the cost of the cards.
  •   A-Card: If the card deck contains less than two cards, draw all the cards from the card deck; otherwise, draw two cards from the top of the card deck.
  •   B-Card: Deal X damage to your enemy.

Note that different B-Cards may have different X values.
At the beginning, you have no cards in your hands. Your enemy has P Hit Points (HP). The card deck has N A-Cards and M B-Cards. The card deck has been shuffled randomly. At the beginning of your turn, you draw a card from the top of the card deck. You can use all the cards in your hands until you run out of it. Your task is to calculate the probability that you can win in this turn, i.e., can deal at least P damage to your enemy.

技术分享
 

 

Input
The first line is the number of test cases T (T<=10).
Then come three positive integers P (P<=1000), N and M (N+M<=20), representing the enemy’s HP, the number of A-Cards and the number of B-Cards in the card deck, respectively. Next line come M integers representing X (0<X<=1000) values for the B-Cards.
 

 

Output
For each test case, output the probability as a reduced fraction (i.e., the greatest common divisor of the numerator and denominator is 1). If the answer is zero (one), you should output 0/1 (1/1) instead.
 

 

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
 

 

Author
SYSU
 

 

Source

Solution:
状压DP.
我第一次设计的DP状态是:
$\text{dp}[s][i]:$ 当前已经抽得的卡的集合是$s$, 还剩下$i$次抽卡机会的方案数.
但是MLE了... 算了一下, 发现这个$\text{dp}$数组确实开不下, 后来想到$i$只和$s$有关, 也就意味着根本不需要\text{dp}的第二维.
设$s$中有$x$张A-Card, $y$张B-Card, 那么剩余的抽卡次数就是$2x+1-(x+y) = x-y+1$, 但是这样改过之后就陷入无尽的TLE当中了,
这个做法的复杂度是$O((m+n)2^{m+n})$, 竟然卡常数...

 

我第一发TLE的NAIVE写法:
#include <bits/stdc++.h>
using namespace std;

typedef long long LL;




const int N{20};
int T, n, m, p;
int a[N];

LL dp[1<<20];


int calc(int s){
    int res=0;
    for(int i=0; i<m; i++)
        if(s&1<<i) res+=a[i];
    return res;
}

int ones(int s){
    int res=0;
    for(int i=0; i<n+m; i++)
        res+=bool(s&1<<i);
    return res;
}

int r(int s){
    int x=0, y=0;
    for(int i=0; i<(n+m); i++)
        if(s&1<<i){
            x++;
            if(i>=m) y++;
        }
    return 2*y+1-x;
}

// 33554432

int main(){

    LL f[N]{1};
    for(int i=1; i<N; i++)
        f[i]=f[i-1]*i;


    for(cin>>T; T--; ){
        cin>>p>>n>>m;
        for(int i=0; i<m; i++)
            cin>>a[i];

        int tot=m+n;

        memset(dp, 0, sizeof(dp));
        dp[0]=1;

        for(int s=0; s<1<<tot; s++)
            if(dp[s] &&r(s)>0)
                for(int j=0; j<tot; j++)
                    if(!(s&1<<j))
                        dp[s|1<<j]+=dp[s];

        LL res=0;
        int full=(1<<tot)-1;

        for(int s=0; s<1<<tot; s++)
            if(calc(s)>=p && (r(s)==0 || s==full))
                res+=dp[s]*f[tot-ones(s)];

        // cout<<res<<endl;

        LL gcd=__gcd(res, f[tot]);
        printf("%lld/%lld\n", res/gcd, f[tot]/gcd);
    }
}
最后一发TLE的写法:
#include <bits/stdc++.h>
using namespace std;

typedef long long LL;




const int N{1<<5};
int T, n, m, p;

int a[N], ones[1<<20];

LL dp[1<<20], f[N]{1};


inline int calc(int s){
    int res=0;
    for(int i=0; i<m; i++)
        if(s&1<<i) res+=a[i];
    return res;
}


inline int r(int s){
    int res=0;
    for(int i=0; i<m; i++)
        res+=bool(s&1<<i);
    // return 2*(ones[s]-res)+1-ones[s];
    return ones[s]-(res<<1)+1;
}

// 33554432

int main(){

    for(int i=0; i<1<<20; i++)
        for(int j=0; j<20; j++)
            if(i&1<<j) ones[i]++;


    for(int i=1; i<N; i++)
        f[i]=f[i-1]*i;


    for(scanf("%d", &T); T--; ){
        scanf("%d%d%d", &p, &n, &m);
        for(int i=0; i<m; i++)
            scanf("%d", a+i);

        // LL res=0;

        int tot=m+n;
        LL res=0, full=(1<<tot)-1;

        if(calc(full)>=p){

            memset(dp, 0, sizeof(dp));
            dp[0]=1;
            for(int s=0; s<1<<tot; s++)
                if(dp[s])
                    if(r(s)==0 || s==full){
                        if(calc(s)>=p) res+=dp[s]*f[tot-ones[s]];
                    }
                    else{
                        for(int j=0; j<tot; j++)
                            if(!(s&1<<j))
                                dp[s|1<<j]+=dp[s];
                    }
        }

        LL gcd=__gcd(res, f[tot]);
        printf("%lld/%lld\n", res/gcd, f[tot]/gcd);
    }
}

这个写法赛后在题库中AC了, 跑了907ms...

 

AC的姿势:

#include <bits/stdc++.h>
using namespace std;

typedef long long LL;




const int N{1<<5};
int T, n, m, p;

int a[N], ones[1<<20];

LL dp[1<<20], f[N]{1};


inline int calc(int s){
    int res=0;
    for(int i=0; i<m; i++)
        if(s&1<<i) res+=a[i];
    return res;
}


inline int r(int s){
    int res=0;
    for(int i=0; i<m; i++)
        res+=bool(s&1<<i);
    // return 2*(ones[s]-res)+1-ones[s];
    return ones[s]-(res<<1)+1;
}

// 33554432

int main(){

    for(int i=0; i<1<<20; i++)
        for(int j=0; j<20; j++)
            if(i&1<<j) ones[i]++;


    for(int i=1; i<N; i++)
        f[i]=f[i-1]*i;


    for(scanf("%d", &T); T--; ){
        scanf("%d%d%d", &p, &n, &m);

        for(int i=0; i<m; i++)
            scanf("%d", a+i);

        // LL res=0;

        int tot=m+n;
        LL res=0, full=(1<<tot)-1;

        if(calc(full)>=p){
            memset(dp, 0, sizeof(dp));
            dp[0]=1;
            for(int s=0; s<1<<tot; s++)
                if(dp[s])
                    if(calc(s)>=p) res+=dp[s]*f[tot-ones[s]];
                    else if(r(s)>0)
                        for(int j=0; j<tot; j++)
                            if(!(s&1<<j))
                                dp[s|1<<j]+=dp[s];
        }

        LL gcd=__gcd(res, f[tot]);
        printf("%lld/%lld\n", res/gcd, f[tot]/gcd);
    }
}

这个跑了358ms.

 

Conclusion:

1. 剪枝

2. 预处理$\text{ones}$表, $\text{ones}[i]$表示$i$的二进制表达式中$1$的个数.


 

这题应该还有复杂度更优的做法, 之后再补充.

 

 

 
 

HDU 5816 Hearthstone

标签:

原文地址:http://www.cnblogs.com/Patt/p/5754266.html

(0)
(0)
   
举报
评论 一句话评论(0
登录后才能评论!
© 2014 mamicode.com 版权所有  联系我们:gaon5@hotmail.com
迷上了代码!