状压DP

HDU1074,这题的大意是一个人需要完成他的作业,但是他的老师给他定了期限,每超过期限一天,就扣一分,问你如何安排做作业的顺序,才能使扣的分数最少,输出要扣的最小分数,并输出完成作业的顺序。(科目数目n<=15)


如果把n个科目进行排序,那么复杂度就有n!,肯定爆炸。其实每一个状态对于一种科目,只有两种可能,一是这个状态下做这个科目,二是不做这个科目,就是1,0两种状态,这样,我们就可以利用二进制很轻松的表示出来(比如101就是这个状态已经做了第一个科目和第三个科目)。利用二进制把状态用0,1表示,就是所谓的状态压缩,用这种方法,我们就可以在(2^n)*n的复杂度下解决这个问题,我们可以明显的看出,状态压缩一般也只能解决n<=20的问题,再大的话,时间也是不够的。


具体一点,就是用二进制的数表示当前的状态下的最优解。针对于这题,我们可以从一个状态,找到一个没有写的作业(就是为0的某一位),进行转移,最终(2^n - 1)这个状态就是最终完成所有作业的状态,它保存的答案也就是题目要求的最优解。

#include<cstdio>
#include<cstring>
#include<iostream>
#include<algorithm>

using namespace std;

const int maxn = (1<<15) + 5;
int dp[maxn];
int d[20],f[20];//数组d是期限,数组f是完成需要的时间
int pre[maxn],t[maxn];//数组pre是用来记录当前状态是由哪个状态推过来的,数组t是记录到当前状态用了多长时间
char a[20][110];

void output(int x)
{
    if(!x)
        return;
    output(x-(1<<pre[x]));
    printf("%s\n",a[pre[x]]);
}
int main(void)
{
    int T,n,m,i,j,inf = 1e9;
    scanf("%d",&T);
    while(T--)
    {
        scanf("%d",&n);
        for(i=0;i<n;i++)
            scanf("%s %d %d",a[i],&d[i],&f[i]);
        m = 1<<n;
        for(i=1;i<m;i++)//状态0表示一个科目都没完成,状态2^n-1表示全部科目都已完成
        {
            dp[i] = inf;
            for(j=n-1;j>=0;j--)
            {
                int tem = 1<<j;//表示第j个科目
                if((tem&i) == 0)//如果状态i还没有完成j,肯定不能由i-j推过来
                    continue;
                int s = t[i-tem] + f[j] - d[j];//表示扣的分数
                if(s < 0)//扣的分数最小为0
                    s = 0;
                if(dp[i] > dp[i-tem] + s)
                {
                    dp[i] = dp[i-tem] + s;
                    t[i] = t[i-tem] + f[j];
                    pre[i] = j;
                }
            }
        }
        printf("%d\n",dp[m-1]);
        output(m - 1);//这里用了递归的方法进行输出
    }

    return 0;
}
/*
input:
2
3
Computer 3 3
English 20 1
Math 3 2
3
Computer 3 3
English 6 3
Math 6 3


output:
2
Computer
Math
English
3
Computer
English
Math
*/

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值