hdu 1074 Doing Homework 状态压缩dp

题目链接

题意:有n个作业,每个作业都有完成所需要的天数以及最迟上交日期,如果迟与上交日期在上交,每天都会被扣一分,问最少要被扣多少分,并输出完成作业的顺序,如果有好几种方案,输出字典序最小的那个。

思路:状态压缩dp,因为一共最多就15种作业,所以可以把每种作业完成与否用0和1表示,0表示没写,1表示写了,则我们可以得到一个二进制数i,代表作业的完成状态,用dp[i]表示当状态到达i时最少扣的分数,dp[i]可由所有dp[1-(1<<j)]推出,j是第j中作业,并且在i表示的状态中,第j种作业已经写了,另外开一个spend数组,spend[i]记录达到状态i一共需要的天数,就是状态i中每种写完的作业所需要的时间的和。最后得到的dp[(1<<n)-1]就是扣的最少的分。那么怎么输出字典序最小的那种情况呢?我们先开一个ans数组,用来存最后的答案,一开始另cur=(1<<n)-1,也就是最后的状态,我们看它是由那一个状态推出来的,我们就吧它存起来,如果有好几个的话,我们就存字典序最大的那个(因为沃我们现在求得顺序和实际答案是相反的,所以是求字典序最大的那个),然后改变cur的值,也就是把cur变成推出cur的那个状态值,再继续下去,直到我们求出n个答案之后,结束循环,把答案倒着输出来就可以了。


#include<cstdio>
#include<cstring>
#include<iostream>
using namespace std;

int dp[1<<16];
struct subject{
    char s[120];
    int d,x;
}a[20];
int spend[1<<16];
char ans[20][110];

int main()
{
    int cas;
    cin>>cas;
    while(cas--){
        memset(dp,0x3f3f,sizeof(dp));
        memset(spend,0,sizeof(spend));
        int n;
        cin>>n;
        for(int i=0;i<n;i++)
            scanf("%s %d %d",a[i].s,&a[i].d,&a[i].x);
        dp[0]=0;
        for(int i=1;i<(1<<n);i++)
        for(int j=0;j<n;j++) if(i&(1<<j)){
            spend[i]=spend[i-(1<<j)]+a[j].x;
            //cout<<spend[i]<<' '<<i<<endl;;
            break;
        }
        //cout<<endl;
        for(int i=1;i<(1<<n);i++){
            for(int j=0;j<n;j++) if(i&(1<<j)){
                int k=i-(1<<j);
                if(a[j].x+spend[k]>a[j].d)
                    dp[i]=min(dp[i],dp[k]+a[j].x+spend[k]-a[j].d);
                else dp[i]=min(dp[i],dp[k]);
                //cout<<i<<' '<<dp[i]<<endl;
            }
        }
        cout<<dp[(1<<n)-1]<<endl;
        int cur=(1<<n)-1;
        char ch[120]="aa";
        int pos;
        for(int i=0;i<n;i++){
            bool flag=1;
            for(int j=0;j<n;j++) if(cur&(1<<j)){
                //cout<<1<<endl;
                int k=cur-(1<<j),x;
                if(spend[k]+a[j].x>a[j].d)
                    x=spend[k]+a[j].x-a[j].d;
                else x=0;
                if(dp[k]+x==dp[cur]){
                    if(flag){
                        flag=0;
                        strcpy(ch,a[j].s);
                        pos=j;
                    }
                    else if(strcmp(ch,a[j].s)<0){
                        strcpy(ch,a[j].s);
                        pos=j;
                    }
                }
            }
            strcpy(ans[i],ch);
            cur-=(1<<pos);
        }
        for(int i=n-1;i>=0;i--)
            cout<<ans[i]<<endl;
    }
    return 0;
}


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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值