ZOJ3777 Problem Arrangement(状态压缩dp,二进制)

描述

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的矩阵,矩阵中的元素a[i][j]代表第i道题放在第j个位置上所获得的积分值,给了两个数nm,其中m代表一个规定的积分值,问如何安排这些题目的顺序可以使所获得的积分大于等于m,最后输出大于等于m的方案数在总方案数的占比。

今天比赛的时候只想到了枚举全排列暴力,但是没有想到状态压缩。

我们可以这样考虑,对于选题目,因为给出的n特别小,所以我们可以用枚举子集的方法来枚举所有的状态。
比如:n=3
它的二进制状态有 2 3 个:

  • 000
  • 001
  • 010
  • 011
  • 100
  • 101
  • 110
  • 111

我们可以按照每一位的1来考虑,状态为:

  • 选一道题的状态: 001,010,100
  • 选两道题的状态:011,101,110
  • 选三道题的状态:111

dp[i][j]表示选题目的状态为i,所获得的兴趣值为j的方案数
对于每一种状态我们可以想一下状态如何转移,当当前只选了一道题的时候,比如当前状态为(010),我们找一下哪些位置还没有被选,比如当前的第1位没有被选,那么:010|100=110,就可以由010转换成110这个状态,对于其他的同理,每次往上加一位,就可以枚举万所有的状态。

那么做法是:

  1. 枚举子集从0到 2 n 1
  2. 计算当前的状态选了几道题
  3. 枚举当前还未被选择的位置
  4. 对于能继续选择的位置,枚举0~m个价值。

最后的状态转移方程是:

d p [ i | ( 1 << ( j 1 ) ) ] [ m i n ( a [ c n t + 1 ] [ j ] + k , m ) ] + = d p [ i ] [ k ] ;

最后求出n!dp[1<<(n-1)][m]的最大公约数,求出方案数占比就行了

代码

#include <cstdio>
#include <cstring>
#include <cctype>
#include <stdlib.h>
#include <string>
#include <map>
#include <iostream>
#include <sstream>
#include <set>
#include <stack>
#include <cmath>
#include <queue>
#include <vector>
#include <algorithm>
#include<list>
using namespace std;
#define mem(a,b) memset(a,b,sizeof(a))
#define lson l,m,rt<<1
#define rson m+1,r,rt<<1|1
#define inf 0x3f3f3f3f
typedef long long ll;
const int N=500+50;
int a[20][20],n,m;
int dp[1<<13][N];
int main()
{
    int t;
    scanf("%d",&t);
    while(t--)
    {
        scanf("%d%d",&n,&m);
        for(int i=1; i<=n; i++)
            for(int j=1; j<=n; j++)
                scanf("%d",&a[i][j]);
        mem(dp,0);
        dp[0][0]=1;
        for(int i=0; i<(1<<n); i++)//枚举子集
        {
            int cnt=0;//计算当前选择了几道题
            for(int j=1; j<=n; j++)
                if(i&(1<<(j-1)))
                    cnt++;
            for(int j=1; j<=n; j++)//枚举当前还能选的位置
            {
                if(i&(1<<(j-1)))continue;
                for(int k=0; k<=m; k++)//枚举每个价值
                    dp[i|(1<<(j-1))][min(a[cnt+1][j]+k,m)]+=dp[i][k];
            }
        }
        if(dp[(1<<n)-1][m]==0)
            puts("No solution");
        else
        {
            int zi=dp[(1<<n)-1][m],mu=1;
            for(int i=1; i<=n; i++)mu*=i;
            int gg=__gcd(zi,mu);
            printf("%d/%d\n",mu/gg,zi/gg);
        }
    }
    return 0;
}

猜你喜欢

转载自blog.csdn.net/riba2534/article/details/80071290
今日推荐