zoj3777 Problem Arrangement(状态压缩dp)

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


题意:给出n道题目以及每一道题目不同时间做的兴趣值,让你求出所有做题顺序中兴趣值不小于m的比例。按一个分数表示。


分析:首先想到的肯定是深搜,深搜枚举一个全排列,然后同时求和,看和大于等于m有多少种,输出结果,但是n的范围是(0--12)12!不能满足深搜的时间限

制,所以在比赛的时候华丽的超时了。


后面,想到了用dp来做,用dp【i】【j】来表示做了 i 道题目的趣味值为 j 的个数。那么可以用dp【i】【k+a【x】【i】】+=dp【i-1】【k】;但是发现其中的 x 是一个

不好控制的量,x表示从之前没有取过的行里面的所有行,但是怎么表示一个行有没有取过呢。。。是一个难题!!!想到用vector,但是发现它每次还是转移的,那

么我们是不是可以转移呢,但是发现转移后他有可能不只表示一种的,卡到这儿写不下去。


其实是个状态压缩dp,状态压缩就是把一些状态较少的情况用二进制的1表示出现0表示没有出现来表示,这样不仅表示方便快速,转移也方便

前面不能表示的状态我们可以用一个状态压缩dp来表示,///dp[i][j]表示取i的二进制位为1的值兴趣值为j时的个数

那么我们就可以转移了。dp[i+(1<<(j-1))][k+a[tmp+1][j]] += dp[i][k];  


代码:

#include <cstdio>
#include <cstring>
const int N = 13;
int dp[1<<13][510];   ///dp[i][j]表示取i的二进制位值兴趣值为j时的个数
int f[N];   ///所有可能情况
int a[N][N];

void isit()
{
    f[1]=1;
    for(int i=2;i<N;i++)
        f[i]=f[i-1]*i;
}
int gcd(int a,int b)
{
    if(b==0)
        return a;
    else
        return gcd(b,a%b);
}
int main()
{
    int T,m,n;
    scanf("%d",&T);
    isit();
    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]);
        memset(dp,0,sizeof(dp));
        dp[0][0]=1;
        for(int i=0;i<=(1<<n);i++)   ///二进制能表示的情况
        {
            int tmp=0;   ///表示已经表示过的题数
            for(int j=1;j<=n;j++)
                if(i&(1<<(j-1)))
                    tmp++;
            //printf("%d\n",tmp);
            for(int j=1;j<=n;j++)
            {
                if(i&(1<<(j-1))) continue;
                for(int k=0;k<=m;k++)
                {
                    if(k + a[tmp+1][j] >= m)   ///把大于值得全部保存到j
                        dp[i+(1<<(j-1))][m] += dp[i][k];
                    else
                        dp[i+(1<<(j-1))][k+a[tmp+1][j]] += dp[i][k];
                }
            }
        }
        if(dp[(1<<n)-1][m] == 0)
            printf("No solution\n");
        else
        {
            int tm = gcd(f[n],dp[(1<<n)-1][m]);
            printf("%d/%d\n",f[n]/tm, dp[(1<<n)-1][m]/tm);
        }
    }
}

搜索超时代码:

#include <iostream>
#include <cstdio>
#include <cstring>
#include <cmath>
#include <algorithm>
#include <vector>
#include <string>
#include <stack>
#include <string.h>
using namespace std;

int map[20][20];
bool flag[20];

int n,m,ans;

void dfs(int p,int sum)
{
    if(p == n)
    {
        if(sum >= m)
            ans ++;
        return ;
    }
    for(int i = 0; i < n; ++ i)
    {
        if(flag[i] == 0)
        {
            flag[i] = 1;
            dfs(p+1,sum+map[i][p]);
            flag[i] = 0;
        }
    }
}

int xxx(int x)
{
    int ans = 1;
    for(int i = 2; i <= x; ++ i)
        ans *= i;
    return ans;
}

int gcd(int a,int b)
{
    if(a<b)
        swap(a,b);
    while(b)
    {
        int temp = a%b;
        a=b;
        b=temp;
    }
    return a;
}

int main()
{
    int t;
    scanf("%d",&t);
    while(t--)
    {
        ans = 0;
        memset(flag,0,sizeof(flag));
        scanf("%d %d",&n,&m);
        for(int i = 0; i < n; ++ i)
            for(int j = 0; j < n; ++ j)
                scanf("%d",&map[i][j]);
        dfs(0,0);
        if(ans == 0)
            puts("No solution");
        else
        {
            int ta=xxx(n);
            int g=gcd(ta,ans);
            ta /= g;
            ans /= g;
            printf("%d/%d\n",ta,ans);
        }
    }
    return 0;
}


  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值