题意:有n(n<=12)个题目,i题目放在j位置会产生p[i][j]的分数,求分数大于等于m的方案数以及总的方案数。
思路:设数组dp[i][j]表示当前d个(d为状态i中1的个数)题目位置摆好之后位置情况的状态为i,分数为j(j<m)的方案数
当j=m时,dp[i][m]为分数>=m的方案数,由此可以建立状态转移方程dp[i+(1<<k)][mm]+=dp[i][j](k为i中二进制的非零位)
可以先预处理把二进制状态里有i个1的数放进num[i]中,以及i对应的各个非零位也可以预处理存起来。
#include<cstdio>
#include<algorithm>
#include<cmath>
#include<vector>
#include<cstring>
using namespace std;
#define NUM (1<<12)
vector < int > l[13];
vector < int > e[NUM];
int n,m,t;
int dp[NUM][501];
int p[13][13];
char c[33];
int f[13];
int gcd(int a,int b)
{
int t;
while(b!=0)
{
t=a;
a=b;
b=t%b;
}
return a;
}
int main()
{
int x,y;
int mm;
f[1]=1;
for(int i=2;i<13;++i)
f[i]=f[i-1]*i;
for(int i=1;i<NUM;++i)
{
l[__builtin_popcount(i)].push_back(i);//预处理有i个1的数
for(int j=0;j<12;++j)
if(!((i>>j)&1))
e[i].push_back(j);//预处理i有几个非零位
}
scanf("%d",&t);
while(t--)
{
scanf("%d%d",&n,&m);
for(int i=0;i<n;++i)
for(int j=0;j<n;++j)
scanf("%d",&p[i][j]);
memset(dp,0,sizeof(dp));
for(int i=0;i<n;++i)//初始化dp
if(p[0][i]<m)
dp[1<<i][p[0][i]]=1;
else
dp[1<<i][m]=1;
for(int i=1;i<n;++i)
for(int j=0;j<l[i].size();++j)
{
if(l[i][j]>=(1<<n))break;
x=l[i][j];
for(int z=0;z<=m;++z)
{
if(dp[x][z]==0)continue ;
for(int k=0;k<e[x].size();++k)
{
if(e[x][k]>=n)break;
y=e[x][k];
mm=(z+p[i][y]>=m)?m:(z+p[i][y]);
dp[x+(1<<y)][mm]+=dp[x][z];
}
}
}
int fenmu=dp[(1<<n)-1][m];
if(fenmu==0)
printf("No solution\n");
else
{
int gcd1=gcd(f[n],fenmu);
printf("%d/%d\n",f[n]/gcd1,fenmu/gcd1);
}
}
}