The 11th Zhejiang Provincial Collegiate Programming Contest is coming! As a problem setter, Edward is going to arrange the order of the problems. As we know, the arrangement will have a great effect on the result of the contest. For example, it will take more time to finish the first problem if the easiest problem hides in the middle of the problem list.
There are N problems in the contest. Certainly, it's not interesting if the problems are sorted in the order of increasing difficulty. Edward decides to arrange the problems in a different way. After a careful study, he found out that the i-th problem placed in the j-th position will add Pij points of "interesting value" to the contest.
Edward wrote a program which can generate a random permutation of the problems. If the total interesting value of a permutation is larger than or equal to M points, the permutation is acceptable. Edward wants to know the expected times of generation needed to obtain the first acceptable permutation.
Input
There are multiple test cases. The first line of input contains an integer T indicating the number of test cases. For each test case:
The first line contains two integers N (1 <= N <= 12) and M (1 <= M <= 500).
The next N lines, each line contains N integers. The j-th integer in the i-th line is Pij (0 <= Pij <= 100).
Output
For each test case, output the expected times in the form of irreducible fraction. An irreducible fraction is a fraction in which the numerator and denominator are positive integers and have no other common divisors than 1. If it is impossible to get an acceptable permutation, output "No solution" instead.
Sample Input
2 3 10 2 4 1 3 2 2 4 5 3 2 6 1 3 2 4
Sample Output
3/1No solution
题意:有n道题,输入一个n*n的矩阵,mp[i][j]表示第i道题放在第j个做的时候的愉悦值,求愉悦值大于等于m的方案有多少种
分析:
方法一:状压dp,如果我们用普通的dp方程,假设dp[i][j]表示到第i个的时候愉悦值为j的有多少种方法,但是这样我们无法表示前面i个用了哪几个位置,于是我们想到用状压,i的哪几个位数为1,表示哪几个位置已经被用掉了,那么接下来要用的第几个题目=i为1的位数(假设题目数从0开始,如果题目数从1开始,接下来要用的第几个题目=i为1的位数+1),知道了要用哪一个题目,我们可以枚举把这个题目放在哪个位置做
#include <map> #include <set> #include <stack> #include <queue> #include <cmath> #include <ctime> #include <vector> #include <cstdio> #include <cctype> #include <cstring> #include <cstdlib> #include <iostream> #include <algorithm> using namespace std; #define INF 0x3f3f3f3f #define inf -0x3f3f3f3f #define lson l,m,rt<<1 #define rson m+1,r,rt<<1|1 #define mem0(a) memset(a,0,sizeof(a)) #define mem1(a) memset(a,-1,sizeof(a)) #define mem(a, b) memset(a, b, sizeof(a)) #define eps 1e-13 const int maxn=1<<12; typedef long long ll; ll dp[maxn][510]; int mp[13][13]; long long gcd(long long x,long long y){ return y==0?x:gcd(y,x%y); } int main(){ int _,m,n; scanf("%d",&_); while(_--){ scanf("%d%d",&n,&m); for(int i=0;i<(1<<n);i++) mem0(dp[i]); for(int i=0;i<n;i++) for(int j=0;j<n;j++) scanf("%d",&mp[i][j]); dp[0][0]=1; for(int j=0;j<(1<<n);j++){ int num=0; for(int k=0;k<n;k++) //num表示已经放了几位 if(j&(1<<k)) num++; for(int k=0;k<n;k++){ //k表示下一个数放在哪一位 if(j&(1<<k)) //这一位已经有数字了 continue; for(int i=0;i<=m;i++) dp[j+(1<<k)][min(m,i+mp[num][k])]+=dp[j][i]; } } ll ans=dp[(1<<n)-1][m]; if(ans==0){ printf("No solution\n"); continue; } ll another=1; for(int i=1;i<=n;i++) another*=i; printf("%lld/%lld\n",another/gcd(another,ans),ans/gcd(another,ans)); } return 0; }
方法二:
折半搜索
首先我们如果直接枚举的话,时间复杂度最坏将会是12^12
但如果我们将其拆分成两半进行枚举的话,一半时间复杂度是12^6,每一半有C12,6种可能,这样的时间复杂度在承受范围之内
如果,我们知道了第一块中有哪些情况,第二块中有哪些情况,我们可以枚举第一块的可能情况,二分查找第二块中可能的情况
#include <map> #include <set> #include <stack> #include <queue> #include <cmath> #include <ctime> #include <vector> #include <cstdio> #include <cctype> #include <cstring> #include <cstdlib> #include <iostream> #include <algorithm> using namespace std; #define INF 0x3f3f3f3f #define inf -0x3f3f3f3f #define lson l,m,rt<<1 #define rson m+1,r,rt<<1|1 #define mem0(a) memset(a,0,sizeof(a)) #define mem1(a) memset(a,-1,sizeof(a)) #define mem(a, b) memset(a, b, sizeof(a)) #define eps 1e-13 const int maxn=1<<12; vector<int>G1[maxn],G2[maxn]; int mp[13][13],flag[13],n; void dfs1(int u,int ans,int sum){ if(u==n/2-1){ G1[ans].push_back(sum); return ; } for(int i=0;i<n;i++) //第几个位置 if(flag[i]==0){ //第几个数 flag[i]=1; dfs1(u+1,ans|(1<<i),sum+mp[u+1][i]); flag[i]=0; } } void dfs2(int u,int ans,int sum){ if(u==n-1){ G2[ans].push_back(sum); return ; } for(int i=0;i<n;i++) //第几个位置 if(flag[i]==0){ //第几个数 flag[i]=1; dfs2(u+1,ans|(1<<i),sum+mp[u+1][i]); flag[i]=0; } } long long gcd(long long x,long long y){ return y==0?x:gcd(y,x%y); } int main(){ int _,m; scanf("%d",&_); while(_--){ scanf("%d%d",&n,&m); for(int i=0;i<(1<<n);i++){ G1[i].clear(); G2[i].clear(); } for(int i=0;i<n;i++) for(int j=0;j<n;j++) scanf("%d",&mp[i][j]); mem0(flag); dfs1(-1,0,0);//时间复杂度最大为12^6,约为3*10^6 mem0(flag); dfs2(n/2-1,0,0); for(int i=0;i<(1<<n);i++){ if(G1[i].size()!=0||G2[i].size()!=0){ sort(G1[i].begin(),G1[i].end()); sort(G2[i].begin(),G2[i].end()); } } long long ans=0,ans1=(1<<n)-1; for(int i=0;i<(1<<n);i++){ if(G1[i].size()!=0){ //最多有C12,6个不为0 int ans2=ans1^i; for(int j=0;j<G1[i].size();j++){ //G1[i].size()最多为6! int v=G1[i][j]; int num=G2[ans2].end()-lower_bound(G2[ans2].begin(),G2[ans2].end(),m-v);//时间复杂度最大为log2(6!) ans+=num; } } } //所以时间复杂度最大为12*11*10*9*8*7*log2(6!),约为7*10^6 if(ans==0){ printf("No solution\n"); continue; } long long another=1; for(int i=1;i<=n;i++) another*=i; printf("%lld/%lld\n",another/gcd(another,ans),ans/gcd(another,ans)); } return 0; }