HDU - 1074 Doing Homework 状压DP

题意:

给定n<=15个任务,每个任务给定名称(按照字典序排列),给定这个任务的截止时间跟花费的时间,如果过了截止时间还未完成,则过一分钟惩罚1,问惩罚的最小值。

分析:

因为n比较小,并且是让你安排一个排列,所以这就是经典的状压DP。

用S表示状态,二进制1代表做了这个任务,0表示没有做。

从而可以得出状态转移方程(如果当前状态 i 中包含了 j 这个子任务)dp[i]=dp[i-(1<<j)]+cost

因为如果当前状态 i 是最优的值的话,那么他的子状态i-(1<<j)肯定也是最优的,从而可以由子状态更新当前状态。

那么问题来了,如何计算 cost 呢?

想一下我们可以很清楚的知道,cost一定等于i-(1<<j)状态所画的总时间T,加上 j 任务所画的时间 f  减去 j 任务截止时间 d 

如果是负数的化,那么cost一定等于0了,因为不需要任何惩罚。

所以:cost=max(0,t[i-(1<<j)]+f[j]-d[j])

现在又有一个新的问题:T 如何更新。当然这就更简单了,肯定是子状态所用的时间加上当前状态所花费的时间

所以:t[i]=t[i-(1<<j)]+f[j]

从而边大功告成。

要输出路径,只要在更新的时候记录即可。因为是字典序输出,所以枚举子状态时倒叙即可。

// LA4850
#include<bits/stdc++.h>

using namespace std;

const int maxn=1<<16;

int t[maxn];
int dp[maxn];
char str[20][maxn];
int d[maxn];
int f[maxn];
int pre[maxn];

const int inf=0x3f3f3f3f;

void print(int x){
    if(!x) return ;
    print(x^(1<<pre[x]));
    printf("%s\n",str[pre[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",&str[i],&d[i],&f[i]);
        }
        int sum=1<<n;
        for(int i=1;i<sum;i++){
            dp[i]=inf;
            for(int j=n-1;j>=0;j--){        //字典序最小输出。
                if(!(i&(1<<j))) continue;
                int tmp=1<<j;
                int cost=max(f[j]-d[j]+t[i-tmp],0);
                if(dp[i]>dp[i-tmp]+cost){
                    dp[i]=dp[i-tmp]+cost;
                    t[i]=t[i-tmp]+f[j];
                    pre[i]=j;
                }
            }
        }
        printf("%d\n",dp[sum-1]);
        print(sum-1);
    }
    return 0;
}

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值