状压DP——ZOJ 3777

  • 题目链接: http://acm.zju.edu.cn/onlinejudge/showProblem.do?problemCode=3777

  • 题意: 给出一个N*N的数字格,第 i,j 个数字表示第 j 道题放在第 i 个位置做的得分,求最终得分超过M的做题顺序有多少种

  • 分析: 因为N最大为12,若枚举长度为N的所有排列,一共有4亿多种,肯定会超时,所以我们会联想到状态压缩

  • 状态:用一个长度为N的二进制串表示当前做题的状态,二进制串里的 1 的个数表示已经做了多少个题,1 的位置表示做的是第几道题,由于已经做了的题里面由于顺序不一样,所以得分也不同,我们还需要另一个维度来记录在做了同样题数的情况下的分数。即 DP[s][k]表示 做题状态为 s ,做题得分为 k 的情况下,一共有多少种做题顺序

  • 转移方程: 首先初始化,一道题没做(s=0),一分没得(k=0)的做题方法只有1种,即DP[0][0] = 1;然后 从 s=0遍历到 (1<

for(int s=0;s<(1<<N);s++)
{
    for(int j=0;j<N;j++)
    {
        if( (s & (1<<j))==0)
        {
            for(int k = 0; k <= M; ++k) 
            {
                if(DP[s][k]) 
                {
                    int tmp = MIN(k + val[p[s]][j], M);
                    DP[s|(1<<j)][tmp] += DP[s][k];
                }
            }
        }
    }
}
  • AC代码:
/*************************************************************************
    > File Name: test.cpp
    > Author: Akira 
    > Mail: qaq.febr2.qaq@gmail.com 
 ************************************************************************/

#include <iostream>
#include <cstdio>
#include <cstring>
#include <string>
#include <cstdlib>
#include <algorithm>
#include <bitset>
#include <queue>
#include <stack>
#include <map>
#include <cmath>
#include <vector>
#include <set>
#include <list>
#include <ctime>
#include <climits>
typedef long long LL;
typedef unsigned long long ULL;
typedef long double LD;
#define MST(a,b) memset(a,b,sizeof(a))
#define CLR(a) MST(a,0)
#define Sqr(a) ((a)*(a))
using namespace std;

#define MaxN 100000
#define MaxM MaxN*10
#define INF 0x3f3f3f3f
#define bug cout<<88888888<<endl;
#define MIN(x,y) (x<y?x:y)
#define MAX(x,y) (x>y?x:y)

template<typename _> inline void scan(_& t)
{
    int c;
    while((c = getchar()) < '0' || c > '9');
    t = c - '0';
    while((c = getchar()) >= '0' && c <= '9') t = t * 10 + c - '0';
}
template<typename _> inline void print(_ x)
{
    int len = 0, p[20];
    if(x < 0) putchar('-'), x = -x;
    while(x) p[++len] = x % 10, x /= 10;
    if(!len) p[++len] = 0;
    while(len) putchar(p[len--] + '0');
}
int T;
int N, M;
int val[13][13];
int fac[13];
int p[1<<13];
void init()  
{  
    fac[0] = 1;  
    for(int i = 1; i <= 12; i++)  fac[i] = fac[i-1] * i; 
    for(int i=0;i<(1<<N);i++) 
    {
        int tmp = 0;
        int x = i;
        while(x)
        {
            tmp++;
            x &= (x-1);
        }
        p[i] = tmp;
    }
}
int gcd(int a, int b) {return b == 0 ? a : gcd(b, a%b);  }
int DP[(1<<13)-1][600];

void solve()
{
    init();
    int flag = 0;
    CLR(DP);
    DP[0][0] = 1;
    for(int s=0;s<(1<<N);s++)
    {
        for(int j=0;j<N;j++)
        {
            if( (s & (1<<j))==0)
            {
                for(int k = 0; k <= M; ++k) 
                {
                    if(DP[s][k]) 
                    {
                        int tmp = MIN(k + val[p[s]][j], M);
                        DP[s|(1<<j)][tmp] += DP[s][k];
                    }
                }
            }
        }
    }
    int ans = DP[(1<<N)-1][M];
    if(ans == 0) puts("No solution");
    else 
    {
        int tmp = fac[N];
        int g = gcd(ans, tmp);
        printf("%d/%d\n", tmp/g, ans/g);
    }
}
int main()
{
    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", &val[i][j]);
            }
        }
        solve();
    }
    //system("pause");
}
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值