题意:有两种牌,A:可以从牌堆摸两张牌,B:可以对敌人造成x点伤害。牌堆有n张A和m张B,一开始摸一张牌,敌人有p点血,问把一个回合把敌人打死的概率
思路:因为n+m<=20,可以考虑状压dp,用dp[S]表示手中牌为S时的方案数,可以再摸的牌数num为 A的数量*2-A的数量-B的数量+1,1表示开始拥有的一张牌。
对于状态i,状态转移方程为
num>0时, for(int j=0;j<n+m;j++)
if(!(i & 1<<j)) // 摸第j张牌
dp[i | 1<<j]+=dp[i];
#include <iostream>
#include <cstdio>
#include <cstring>
#include <cmath>
#include <algorithm>
#include <stack>
#include <queue>
#include <map>
#include <set>
#include <vector>
#define LL long long
#define eps 1e-8
#define maxn 150
#define mod 110119
#define inf 0x3f3f3f3f
#define IN freopen("in.txt","r",stdin);
using namespace std;
LL p,n,m,a[30];
LL cnt1,cnt2;
LL dp[1<<(21)];
LL f[20];
void init(){
f[0]=1;
for(int i=1;i<=20;i++){
f[i]=i*f[i-1];
}
}
int OK(int S){
cnt1=0;cnt2=0;
for(int i=0;i<n;i++){
if(S & 1<<i ){
cnt1++;
}
}
for(int i=n;i<n+m;i++){
if(S & 1<<i ){
cnt2++;
}
}
return cnt1*2-cnt1-cnt2+1>=0;
}
LL get_p(int S){
LL sum=0;
for(int i=0;i<m;i++){
if(S & 1<<(n+i)){
sum+=a[i];
}
}
return sum;
}
LL gcd(LL a,LL b){
if(b==0) return a;
return gcd(b,a%b);
}
int main(){
// IN;
init();
int t;
cin>>t;
while(t--){
memset(dp,0,sizeof(dp));
scanf("%lld%lld%lld",&p,&n,&m);
for(int i=0;i<m;i++){
scanf("%d",&a[i]);
}
dp[0]=1;
LL ans=0;
for(int i=0;i< 1<<(n+m);i++){
if(!dp[i] || !OK(i)){
continue;
}
if(get_p(i)>=p){
ans+=dp[i]*f[(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];
}
}
}
LL k=gcd(ans,f[n+m]);
printf("%lld/%lld\n",ans/k,f[m+n]/k);
}
return 0;
}