Problem Arrangement ZOJ 3777 状态压缩dp

题目链接
Problem Arrangement

Time Limit: 2 Seconds       Memory Limit: 65536 KB

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/1
No solution

题意:要求你在n*n个数字中找n个数字,这n个数字中的任意两个数不能是同一行或者是同一列。求这n个数字的和大于等于m的概率是多少?(化简之后输出,若不存在输出"No solution")

样例:

2 4 1
3 2 2
4 5 3
2 4 1
3 2 2
4 5 3
样例是这两种组成,总共6(1*2*3)种情况,输出3/1;

思路:因为n比较小,将(前i行,取了哪几列)这个状态压缩成二进制,0到 (1<<n)-1 (总共1<<n个状态);

扫描二维码关注公众号,回复: 191750 查看本文章

int dp[1<<13][505];dp[i][j]代表i这个状态,总和为j的方案有多少种。

1代表已经选了这一列,0代表未选这一列

例如样例n==3

第零行的状态有  000

第一行的状态有  100  010  001

第二行的状态有  110  101  011(第i行只能选i列,行数是和状态中1的个数是相等的)

第三行的状态有  111

那么010这个状态可以加上a[2][1]变成110这个状态
还有001这个状态可以加上a[2][1]变成101这个状态

那么100这个状态可以加上a[2][2]变成110这个状态
还有001这个状态可以加上a[2][2]变成011这个状态

每个状态分成m种(和为 m),由上个状态加上a[i][j]后可以得到哪个状态。

我做的第一道状态压缩dp,因为没看别人的博客,自己写了1个多小时,可能思路代码都比较丑,望轻喷。

代码:

#include <iostream>
#include <stdio.h>
#include <string.h>
#include <string>
#include <map>
#include <queue>
#include <math.h>
#include <time.h>
#include <algorithm>
#define mem(a,b) memset(a,b,sizeof a)
#define LL long long
#define y1 ctx_y1
#define x1 ctx_x1
const LL inf=0x3f3f3f3f;
const LL mod=1e9+7;
const int N=505;
const int M=405;
using namespace std;
vector<int>v[13];//每行可能出现的状态
int dp[1<<13][N];
int a[13][13];//存图
bool vis[1<<13];//标记状态
int main()
{
    int t;
    scanf("%d",&t);
    while(t--)
    {
        int n,m;
        scanf("%d%d",&n,&m);
        for(int i=1; i<=n; i++)
            for(int j=1; j<=n; j++)
                scanf("%d",&a[i][j]);
        v[0].clear();
        v[0].push_back(0);//第0行状态只有0
        for(int i=1; i<=n; i++)
        {
            mem(vis,0);//清空状态的标记
            v[i].clear();
            for(int j=0; j<n; j++)
            {
                int h=1<<j;//枚举第j列
                for(int k=0; k<v[i-1].size(); k++)//第i-1行的状态再加上第j列就是第i行的状态
                {
                    if( !(h&v[i-1][k])&&!vis[h+v[i-1][k]])//第i-1行的状态v[i-1][k]不包含第j列h,并且得到的状态h+v[i-1][k]没有出现过
                    {
                        v[i].push_back(h+v[i-1][k]);//得到的状态加入第i行
                        vis[h+v[i-1][k]]=1;//标记
                    }
                }
            }
        }
        mem(dp,0);
        dp[0][0]=1;
        for(int i=1; i<=n; i++)
        {
            for(int j=1; j<=n; j++)//枚举n*n的每一个位置
            {
                int w=1<<(j-1);//第j列的状态
                int ii=i-1;
                for(int h=0; h<v[ii].size(); h++)//枚举第ii行的所有状态
                {
                    if((v[ii][h]&w)) continue;//如果第ii行的状态包含第j列,则跳过
                    int sum=v[ii][h]+w;//得到的第i行一个状态
                    for(int k=0; k<=m; k++)//枚举前ii行此状态的和
                    {
                        int q=k+a[i][j];//q表示加上a[i][j]得到的和
                        if(q>m) q=m;//大于等于m的和 都存在m中
                        dp[sum][q]+=dp[v[ii][h]][k];//sum是后来的状态,q是后来的和,v[ii][h]是上一行的状态,k是上一行的和。
                    }
                }
            }
        }
        int ans1=dp[(1<<n)-1][m];//(1<<n)-1  为111111111(n个1这种状态),和大于等于m
        if(ans1==0)
        {
            printf("No solution\n");
            continue;
        }
        int ans2=1;
        for(int i=1; i<=n; i++)
            ans2*=i;//总的情况数量为n!
        int ans=__gcd(ans1,ans2);
        printf("%d/%d\n",ans2/ans,ans1/ans);
    }
}


/*

附上两个数据:

2
9 50
2 4 1 1 4 7 8 9 9
3 2 8 7 4 9 5 2 5
4 5 3 6 9 8 7 1 1
2 4 7 8 9 1 1 4 9
3 2 2 8 7 1 7 1 5
4 5 3 6 9 8 7 1 1
4 1 7 8 9 2 9 1 4
3 2 2 8 7 4 9 5 5
4 3 6 9 8 5 7 1 1
9 30
2 4 1 1 4 7 8 9 9
3 2 8 7 4 9 5 2 5
4 5 3 6 9 8 7 1 1
2 4 7 8 9 1 1 4 9
3 2 2 8 7 1 7 1 5
4 5 3 6 9 8 7 1 1
4 1 7 8 9 2 9 1 4
3 2 2 8 7 4 9 5 5
4 3 6 9 8 5 7 1 1

输出:
10080/2161
181440/176693
*/





猜你喜欢

转载自blog.csdn.net/xiangaccepted/article/details/80069093
今日推荐