题目大意:安排一场ACM比赛的题目顺序,每个题目安排在不同的位置可以得到不同的值(矩阵的形式给出),随即一个题目排列,如果每个所有题目的值的总和大于M,则认为这个排列是好的,否则就是不好的。求出随机排列第一次就是好的排列的期望。
从上往下,状压递推,分清楚次数跟分数。由于分数比较小,可以枚举分数
#include <iostream>
#include <cstring>
#include <cstdio>
#include <vector>
using namespace std;
const int MAXN = 12;
const int MAXS = 1<<MAXN;
int n,m;
vector<int> s[2];//s[0]表示上一行的状态,s[1]表示当前行的状态
int cc[MAXS][510];//CC[i][j] 表示状态i 分数j的次数
int a[20][20];
int fact(int n)
{
int r = 1;
for(int i=2;i<=n;i++)
{
r*=i;
}
return r;
}
int gcd(int a,int b)
{
return b==0?a:gcd(b,a%b);
}
/**
区分每个状态的分数和次数的表示
cc数组表示每个状态以一定分数出现的次数
*/
void solve()
{
int ans = 0;
int curRow = 0;
s[0].clear();s[1].clear();
memset(cc,0,sizeof(cc));
for(int i=0;i<n;i++)
{
int k = (1<<i);
if(a[0][i]<m)
{
s[0].push_back(k);
cc[k][a[0][i]]++;
}
else
{
ans +=fact(n-1);
}
}
bool vis[MAXS];
for(int i=1;i<n;i++) //枚举每个问题
{
memset(vis,0,sizeof(vis));
int sz = s[curRow].size();
curRow ^=1;
s[curRow].clear();
for(int j=0;j<n;j++) //第 i 个问题放在位置j
{
for(int k=0;k<sz;k++)
{
int ls = s[1^curRow][k];
if(ls&(1<<j))continue;
for(int u=0;u<m;u++)
{
if(cc[ls][u])
{
int temp = u+a[i][j];
//printf("No.%d pos at %d get %d with org %d\n",i,j,temp,u);
if(temp>=m)
{
ans += cc[ls][u]*fact(n-1-i);
}
else
{
int new_s = ls | (1<<j);
if(!vis[new_s])
{
vis[new_s]=1;
s[curRow].push_back(new_s);
}
cc[new_s][temp] += cc[ls][u];
}
}
}
}
}
}
if(ans==0)
{
printf("No solution\n");
}
else
{
int fenmu = fact(n);
int gg =gcd(fenmu,ans);
printf("%d/%d\n",fenmu/gg,ans/gg);
}
}
int main()
{
freopen("data.in","r",stdin);
int T;
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",&a[i][j]);
}
}
solve();
}
return 0;
}