题目链接在这里
题目大意
有N(N <= 12)道题,第i个解决的问题是题目j的话,就会得到arr[i][j]分。问解决完所有问题之后,得分能超过M的概率有多少。其中分子是有多少种解决问题的方式,分母是得分大于等于M的方案数。
思路分析
思路来自另一位博主,我讲得不是很清楚,可以看他的。链接
N道题一共有N!种排序,也就是有N!种解决方案,所以分子的问题我们已经解决了。
我们考虑dp[i][j]代表做了i种题(比如做了1、3、5题)之后得分为j的方案数。对于i,因为N <= 12,每道题都有做/未做两种状态,所以共有1 << 12种状态,每一种状态对应着一个i。
我们用cnt描述现在已经做了多少题。
比如说我现在做了1、3、5题,想做第2题了。因为第2题是第4个要做的题,所以我们就知道可以得到arr[4][2](C语言里要减一,因为输入从0开始)。那么i = 1<<1 + 1<<3 + 1<<5 ,那么dp[ i + 1 << 2 ] [ j + arr [ 4 ] [ 2 ] ] += dp[ i ][ j ]。为了方便计数,如果j + arr[4][2] >= m的话,我们就直接按m处理就行了。
最后取出dp[1<<n - 1][m]就行了。
代码如下
#include <cstdio>
#include <cstring>
#include <string>
#include <iostream>
#include <algorithm>
#define clr(x) memset(x, 0, sizeof(x))
#define rep(i, n) for(int i = 0; i < n; ++i)
using namespace std;
const int MaxM = 510;
const int MaxN = 12;
int t;
int n, m;
int dp[1 << MaxN][MaxM];
int arr[MaxN][MaxN];
int f[MaxN + 1];
int gcd(int a, int b){
return b ? gcd(b, a % b) : a;
}
void solve(){
clr(dp);
dp[0][0] = 1; //一个没选,得分为0的情况有1个
rep(i, (1 << n)){
int cnt = 0;
rep(j, n)
if(i & (1 << j)) ++cnt; //计数,算目前已经做了多少题
rep(j, n){
if(i & (1 << j)) continue; //如果这道题已经做过了,就略过
for(int g = 0; g <= m; ++g){ //g代表已经得到了多少分
if(g + arr[cnt][j] >= m) //如果g加上做完这道题得到的分数大于m了,就直接按m算。方便最后计算结果
dp[i + (1 << j)][m] += dp[i][g];
else
dp[i + (1 << j)][g + arr[cnt][j]] += dp[i][g];
}
}
}
if(dp[(1 << n) - 1][m]){
int a = dp[(1 << n) - 1][m];
int b = f[n];
int tmp = gcd(a, b);
cout << b / tmp << "/" << a / tmp << endl;
}
else
cout << "No solution" << endl;
}
int main(){
ios::sync_with_stdio(false);
f[0] = 1;
for(int i = 1; i <= MaxN; ++i)
f[i] = f[i - 1] * i; //计算阶乘
cin >> t;
while(t--){
cin >> n >> m;
rep(i, n){
rep(j, n){
cin >> arr[i][j];
}
}
solve();
}
return 0;
}