HDU 1074 Doing Homework (状压+DP) 初学dp笔记

题目链接:http://acm.hdu.edu.cn/showproblem.php?pid=1074


这道题是给你n个任务,每个任务都有 任务名称,任务时限,所需时间,然后当一个任务的完成时间 t 超过该任务的任务时限 end 时,就会扣除 t-end 的分数,然后求完成所有任务所扣除的分数最小,把任务安排打印出来,如果有多种方案,输出字典序最小的。


这题用可以用n位的二进制表示选取情况,在二进制的第i位上,可以用1或0表示第i个物品已选取或者尚未选取,这样子我们要求的答案就是这n位全是1的状态。至于方案输出就是记录路径,可以在状态转移的过程中记录。


我最开始的想法是将状态定义成:选第i个任务的时候选的是编号为j的任务的状态。也就是dp[i][k],k表示的是当前的选取状态,然后当满足(k&(1<<j)==1)&&(k里面的1的个数为i)时,也就是当前选择第i个任务 选择的是编号为j的任务时,可以通过选择了i-1个任务,状态是k^(1<<j)的情况推过来,那么状态转移公式就是  dp[i][k]=min(dp[i][k],dp[i-1][k^(1<<j)]+k^(1<<j)状态选取第j个任务扣除的分数)。



但是仔细想想,如果不限制当前选择的是第几个任务,而是通过当前状态去判断是否可选取编号为j的任务,那么是否可行呢?答案是可行的,我们会发现,知道了当前状态k(0<k<(1<<n)),那么就知道了选取的任务个数,我们可以枚举j,表示最后一个选的任务是编号为j的任务。那么我们需要知道的就是在没选它的时候的状态的最优解,也就是dp[k]=min(dp[k],dp[k^(1<<j)]+k^(1<<j)状态选取第j个任务扣除的分数)


第一种思路:

#include <cstring>
#include <iostream>
#include <stdio.h>
#include <algorithm>
#include <cmath>
#include <vector>
#include <queue>
#include <stack>
#include <map>
#include <math.h>
#include <set>
#include <time.h>
using namespace std;
char s[20][110];
int End[20];
int need[20];
int gs[1<<15];
int dp[17][1<<15][4];
int ans[17];
int main()
{
    for(int i=0; i<(1<<15); ++i)//统计所有的状态二进制1的个数
    {
        int count=0;
        int temp=i;
        while(temp)
        {
            count+=temp&1;
            temp>>=1;
        }
        gs[i]=count;
    }
    int t;
    scanf("%d",&t);
    while(t--)
    {
        memset(dp,0x3f,sizeof(dp));
        int n;
        scanf("%d",&n);
        for(int i=0; i<n; ++i)
            scanf("%s%d%d",s[i],End+i,need+i);
        int ma=1<<n;
        memset(dp[0],0,sizeof(dp[0]));
        for(int i=1; i<=n; ++i)
        {
            for(int j=n-1;~j; --j)
                for(int k=0; k<ma; ++k)
                {
                    int now=k&(1<<(j));
                    if(now&&gs[k]==i)//如果第i位是1,并且k的二进制表示1的个数为i
                    {
                        int temp=dp[i-1][k^(1<<(j))][1]+need[j]-End[j];//需要扣掉的分数
                        if(temp<0)//不扣分
                            temp=0;
                        if(dp[i][k][0]>dp[i-1][k^(1<<(j))][0]+temp)
                        {
                            dp[i][k][0]=dp[i-1][k^(1<<(j))][0]+temp;
                            dp[i][k][1]=dp[i-1][k^(1<<(j))][1]+need[j];//当前选取状况所需的时间
                            dp[i][k][2]=j;//当前选的是编号为j的任务
                            dp[i][k][3]=k^(1<<(j));//从k^(1<<(j))来
                        }
                    }
                }
        }
        printf("%d\n",dp[n][(1<<(n))-1][0]);
        int now=(1<<(n))-1;
        for(int i=n; i; --i)
        {
            ans[i]=dp[i][now][2];
            now=dp[i][now][3];
        }
        for(int i=1; i<=n; ++i)
            printf("%s\n",s[ans[i]]);
    }
    return 0;
}





第二种思路:

#include <cstring>
#include <iostream>
#include <stdio.h>
#include <algorithm>
#include <cmath>
#include <vector>
#include <queue>
#include <stack>
#include <map>
#include <math.h>
#include <set>
#include <time.h>
using namespace std;
char s[20][110];
int End[20];
int need[20];
int dp[1<<16][2];
int ans[1<<16];
void print(int x)//打印路径
{
    if(!x)
        return ;
    print(x^(1<<ans[x]));
    printf("%s\n",s[ans[x]]);
}
int main()
{
    int t;
    scanf("%d",&t);
    while(t--)
    {
        int n;
        scanf("%d",&n);
        for(int i=0; i<n; ++i)
            scanf("%s%d%d",s[i],End+i,need+i);
        int ma=1<<n;
        dp[0][0]=0;//状态的初始值
        dp[0][1]=0;
        for(int i=1; i<ma; ++i)
        {
            dp[i][0]=0x3f3f3f3f;
            for(int j=n-1; ~j; --j)
            {
                if(i&(1<<j))
                {
                    int cj=dp[i^(1<<j)][1]+need[j]-End[j];
                    if(cj<0)
                        cj=0;
                    if(dp[i][0]>dp[i^(1<<j)][0]+cj)
                    {
                        dp[i][0]=dp[i^(1<<j)][0]+cj;
                        dp[i][1]=dp[i^(1<<j)][1]+need[j];
                        ans[i]=j;
                    }
                }
            }
        }
        printf("%d\n",dp[(1<<n)-1][0]);
        print((1<<n)-1);
    }
    return 0;
}


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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值