HDU 1074 Doing Homework 入门 状压dp 输出路径

http://acm.hdu.edu.cn/showproblem.php?pid=1074

题意:

有n个任务,每个任务有一个截止时间,超过截止时间一天,要扣一个分。
求如何安排任务,使得扣的分数最少。

Input

有多组测试数据。第一行一个整数表示测试数据的组数
第一行一个整数n(1<=n<=15) 
接下来n行,每行一个字符串(长度不超过100)表示任务的名称和两个整数,分别表示任务的截止时间和完成任务需要的天数。 这n个任务是按照字符串的字典序从小到大给出。

Output

每组测试数据,输出最少扣的分数的。 并输出一个完成任务的方案,如果有多个方案,输出字典序最小的一个。

思路:

n位二进制表示状态(e.g. n=4):

0000表示一个作业都没写的状态

0001表示写了第一个作业

1111表示所有的作业都写完的状态

某个状态1101 可能由1100 // 1001 // 0101三个状态得到, 那么1101状态的扣分数就由

                   1100扣分数+1100结束的时间+第1个作业的时长-第1个作业的截止时间

                   1001扣分数+1001结束的时间+第3个作业的时长-第3个作业的截止时间

                   0101扣分数+0101结束的时间+第4个作业的时长-第4个作业的截止时间

来更新.

递推式:

dp[i]=min(dp[i],dp[pst]+t[pst]+len[j]-jz[j])

i表示当前状态, pst表示前一个状态, j表示从pst到i新增的作业

注意:

假设当前状态是1101, 那么它的前个状态可能是1100 // 1001 // 0101, 而如果这三个状态得到的dp[1101]都是一样的, 这三个状态最后写的作业分别是作业1, 作业3, 作业4, 显然最后写作业4是字典序最小的, 因此选择由0101转移到的情况.所以, 在实际更新过程中, 应该让字典序大的先来枚举,也就是按照 " 作业4 → 作业3 → 作业1 "的顺序进行更新.

代码:

#include<bits/stdc++.h>
#define fuck(x) std::cout<<"["<<#x<<"->"<<x<<"]"<<endl;
using namespace std;
typedef long long ll;

const int M=16;
const int inf=1e9+5;

int n;
int dp[1<<M];
int bf[1<<M];
int t[1<<M];

string nam[M];
int jz[M];
int len[M];


int main() {
    int _;
    cin>>_;
    while(_--) {
        memset(dp,0,sizeof(dp));
        cin>>n;
        for(int i=0; i<n; i++) {
            cin>>nam[i]>>jz[i]>>len[i];
        }
        fill(dp,dp+(1<<M),inf);
        memset(bf,0,sizeof(bf));
        memset(t,0,sizeof(t));
        int m=1<<n;
        dp[0]=0;
        for(int i=1; i<m; i++) {//枚举每一种状态
            for(int j=n-1; j>=0; j--) {//用每一个作业去更新
                int tem=1<<j;//tem是仅有1位是1的二进制数
                if(i&tem) {//如果i状态已经做过tem代表的(j)作业
                    int pst=i-tem;//pst状态后最后新增作业为j作业的状态是i状态.
                    int change=t[pst]+len[j]-jz[j];//新扣的分数
                    if(change<0)
                        change=0;
                    if(dp[pst]+change<dp[i]) {
                        dp[i]=dp[pst]+change;
                        bf[i]=pst;//bf[i]储存i状态的前1状态
                        t[i]=t[pst]+len[j];//更新现在的时间
                    }
                }
            }
        }
        printf("%d\n",dp[m-1]);
        
        //以下输出状态变化路径
        stack<int>s;
        int a=m-1,b;//m-1是全为1(即最后的)状态
        while(a!=0){
            b=bf[a];//b是a前一个状态
            int x=a-b;//x仅有1位为1,代表a→b新增的作业
            int pos=0;
            for(;;pos++){
                if(x==1)break;
                x>>=1;
            }//取出pos是这个作业的编号
            s.push(pos);
            a=b;
        }
        while(!s.empty()){
            cout<<nam[s.top()]<<endl;
            s.pop();
        }
    }
    return 0;
}

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值