您的位置:首页 > 其它

HDU 5816 Hearthstone (状压dp)

2016-08-10 09:34 267 查看
题目大概说有两种卡牌,使用A牌能从牌堆摸两张牌,使用B牌能对对方造成xi点伤害。在你的回合,你从牌堆摸一张牌,问能对对方造成p点及以上伤害的概率。

要求的其实就是能造成p点以上伤害的牌堆排列数/牌堆全排列数

全排列而且总数为20,这种就该想到尝试用状压DP。。

dp[S]表示已经摸的牌的集合为S的可行排列方案数
对于一个状态S,我们能从这个集合中已经摸的牌知道还能摸几张,即A的数目 * 2 - A的数目 - B的数目 + 一开始能摸的一张牌

考虑状态的转移,我是用我为人人实现的:从小到大枚举状态S,判断S是否合法,即S的方案数不为0且还能摸牌,然后通过S去更新S+i(i∉S)状态的值。另外如果S能造成的伤害已经大于等于p了,那就没必要去更新它能转移到的状态,因为还剩下的牌直接求全排列计算其贡献,这样也能避免重复计算。

最后就循环遍历各个合法的状态累加贡献,这个贡献就是dp值 * 还没摸的牌的全排列数。这样就求出能造成p点以上伤害的牌堆排列数,再和全排列数用GCD搞搞输出答案即可。

没想到排列,老是纠结于概率,然后就g了。。

#include <set>
#include <map>
#include <stack>
#include <queue>
#include <deque>
#include <cmath>
#include <vector>
#include <string>
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <iostream>
#include <algorithm>
using namespace std;
#define L(i) i<<1
#define R(i) i<<1|1
#define INF 0x3f3f3f3f
#define pi acos(-1.0)
#define eps 1e-9
#define maxn 1100010
#define MOD 1000000007
int n,m;
long long p,a[22];
long long dp[maxn],fac[22];
int cnt1,cnt2;

int ok(int x)
{
cnt1 = 0,cnt2 = 0;
for(int i = 0; i < n; i++)
if(x & (1<<i))
cnt1++;
for(int i = 0; i < m; i++)
if(x & (1<<(n+i)))
cnt2++;
return cnt1 - cnt2 + 1 >= 0;
}
long long get_sum(int x)
{
long long sum = 0;
for(int i = 0; i < m; i++)
if(x&(1<<(n+i)))
sum += a[i];
return sum;
}
long long gcd(long long a,long long b)
{
if(b == 0)
return a;
return gcd(b,a%b);
}
int main()
{
int t,C = 1;
scanf("%d",&t);
fac[0] = 1;
for(int i = 1; i < 22; i++)
fac[i] = fac[i-1] * i;
while(t--)
{
scanf("%lld%d%d",&p,&n,&m);
memset(dp,0,sizeof(dp));
long long ans = 0;
for(int i = 0; i < m; i++)
scanf("%lld",&a[i]);
dp[0] = 1;
for(int i = 0; i < 1<<(n+m); i++)
{
if(!dp[i] || !ok(i))
continue;
if(get_sum(i) >= p)
{
ans += dp[i] * fac[n+m-cnt1-cnt2];
continue;
}
if(cnt1-cnt2+1 == 0)
continue;
for(int j = 0; j < n+m; j++)
if(!(i&(1<<j)))
dp[i|(1<<j)] += dp[i];
}
long long k = gcd(ans,fac[n+m]);
printf("%lld/%lld\n",ans/k,fac[n+m]/k);
}
return 0;
}
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: