hdu-1074 Doing Homework (状压dp)

Doing Homework
Time Limit: 2000/1000 MS (Java/Others)    Memory Limit: 65536/32768 K (Java/Others)
Total Submission(s): 9920    Accepted Submission(s): 4750

Problem Description
Ignatius has just come back school from the 30th ACM/ICPC. Now he has a lot of homework to do. Every teacher gives him a deadline of handing in the homework. If Ignatius hands in the homework after the deadline, the teacher will reduce his score of the final test, 1 day for 1 point. And as you know, doing homework always takes a long time. So Ignatius wants you to help him to arrange the order of doing homework to minimize the reduced score.
 
Input
The input contains several test cases. The first line of the input is a single integer T which is the number of test cases. T test cases follow.
Each test case start with a positive integer N(1<=N<=15) which indicate the number of homework. Then N lines follow. Each line contains a string S(the subject's name, each string will at most has 100 characters) and two integers D(the deadline of the subject), C(how many days will it take Ignatius to finish this subject's homework). 

Note: All the subject names are given in the alphabet increasing order. So you may process the problem much easier.
 
Output
For each test case, you should output the smallest total reduced score, then give out the order of the subjects, one subject in a line. If there are more than one orders, you should output the alphabet smallest one.
 
Sample Input
2
3
Computer 3 3
English 20 1
Math 3 2
3
Computer 3 3
English 6 3
Math 6 3
 
Sample Output
2
Computer
Math
English
3
Computer
English
Math

Hint

In the second test case, both Computer->English->Math and Computer->Math->English leads to reduce 3 points, but the 

word "English" appears earlier than the word "Math", so we choose the first order. That is so-called alphabet order.



题目大意:现在你要完成N门作业,每门作业有一个时限和你完成这门作业所需要花费的时间,如果你无法在规定的时限内完成作业,那么每超出一天你的期末成绩都会被扣一分,现在问你你做完所有作业会被扣掉的最少分数是多少?同时输出你完成作业的顺序。

题目思路:要使得最后被扣的分数最少,我们肯定要优先去完成那些花费时间和时限非常接近的作业,让尽可能多的作业是在规定时限内完成,但是我们如果用常规方法的话是无法很好的记录当前完成了哪几个作业,而哪几个又没做。此时就要用到神奇的二进制了,由于本题的N(N <= 15)很小,所以可以考虑用状压dp来解决这一题。以二进制第 i 位上的数是否为1来表示第 i 个作业是否完成了(如,000010001100100就表示了已经完成了第3项,第6项,第7项和第11项作业是已经完成的了);接着我们就可以用该二进制数的十进制来表示当前状态,每次更新的时候就可以知道在完成第 i 项作业之前总共花费了多少时间,被扣掉的分数是多少。

我们便可以推出状态转移方程为:

dp[now].cost = min(dp[pas].cost + cost,dp[now].cost); (其中cost为你当前选择的作业在"pas"的状态基础上做完之后所被扣掉的分数;cost = dp[pas].time + h[i].c - h[i].d)然后再将 dp[now].time = dp[pas].time + h[i].c;更新到现在总共过了多少时间。

本题还有一点要求就是要输出完成作业的顺序,我们只需要加一个pre的变量,记录每次更新的时候你的前一个状态是什么,最后再从完成作业之后的最终状态推回去即可。


具体实现看如下代码:


#include <bits/stdc++.h>
#define INF 0x3f3f3f3f
#define FIN freopen("in.txt","r",stdin)
#define fuck(x) cout<<'['<<x<<']'<<endl
using namespace std;
typedef long long LL;
typedef pair<int, int>pii;
const int MX = 100 + 10;

struct node {
    char name[MX];
    int d, c;//d为时限,c为所需要花费的时间。
} h[20];

struct nn {
    int time, cost, pre, now;
    //time 表示到当前状态已经花费了多少时间,cost表示到当前状态被扣掉的最少分数,pre记录当前状态的前一个状态;now记录当前是哪个点。
} dp[(1 << 15) + 10];

int T, n;

int main() {
    //FIN;
    while(~scanf("%d", &T)) {
        while(T--) {
            scanf("%d", &n);
            memset(dp, 0, sizeof(dp));
            for(int i = 0; i < n; i++)
                scanf("%s%d%d", h[i].name, &h[i].d, &h[i].c);
            int ch = (1 << n) - 1;
            for(int k = 1; k <= ch; k++) {
                dp[k].cost = INF;//将当前状态初始化为INF,来更新最小被扣的分数;
                for(int i = n - 1; i >= 0; i--) {
                    int tmp = 1 << i;//新完成的作业的状态;
                    if(k & tmp) {//如果当前状态并没有完成第i个作业的话我们就跳过该状态;
                        int pas = k - tmp;//得到k这个状态在完成第 i 个作业之前的状态pas;
                        int cost = dp[pas].time + h[i].c - h[i].d;//计算在pas的状态下完成第 i 个作业之后被扣的分数;
                        if(cost < 0)
                            cost = 0;
                        if(cost + dp[pas].cost < dp[k].cost) {//对k状态进行更新;
                            dp[k].cost = cost + dp[pas].cost;
                            dp[k].pre = pas;//记录上一个状态;
                            dp[k].now = i;//记录当前状态;
                            dp[k].time = dp[pas].time + h[i].c;//更新已经过了的时间;
                        }
                    }
                }
            }
            printf("%d\n", dp[ch].cost);
            stack<int>s;//通过压栈来实现正序输出;
            while(ch) {
                s.push(dp[ch].now);
                ch = dp[ch].pre;
            }
            while(!s.empty()) {//弹出栈内元素完成输出
                printf("%s\n", h[s.top()].name);
                s.pop();
            }
        }
    }
    return 0;
}


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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值