1. 程式人生 > >HDU 1074 Doing Homework

HDU 1074 Doing Homework

ces may 數據 bject orm 二進制表示 上交 rom 初始

Doing Homework

  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.

解題思路:
  本題給出多組數據,每組數據給出一個整數t為測試數量,給出一個整數n代表項目數量,之後n行跟隨每行給出一個項目信息,包括項目名name 最晚上交時間deadline 完成該項目需要的時間costDays。每個項目只要晚於deadline所規定的時間上交,每晚一天扣一分。對於每組測試要求輸出最少的扣分數之後按提交順序輸出項目名。

  本題限定了項目的數量n <= 15。假設有A、B、C、D四個項目,每個項目有已上交和未上交兩種狀態,我們可以用一個四位二進制表示這四個項目的所有狀態,例如0001表示A上交其他所有項目未上交,0011表示AB項目上交CD項目未上交,1111表示所有項目都上交,以此類推,若想n取到最大15,需要15位2進制數表示所有情況,表示15個項目全部提交的二進制數最大,為111111111111111,其所代表的10進制整數為32767,其大小遠小於int上限,那麽我們只需要令maxn = 1 << 15 即32768 就可以表示本題中所有可能出現的狀態,

  既然所有狀態都已經可以表示出來,現在就可以考慮一下如何獲得狀態,例如AB項目上交CD項目未上交的狀態0011,可以由A上交其他都沒上交的狀態0001提交B(0010)獲得,也可以由狀態B上交其他都沒上交的狀態0010提交A(0001)獲得,那麽我們如果想要算出獲得當前狀態的最小扣分值,只需要遍歷所有只需一次提交便可以得到當前狀態的狀態(以後稱為前狀態)找到前狀態的扣分加上本次提交項目所需的扣分的最小值即可,這就是本題的解題思想——狀態壓縮dp。

  狀態壓縮dp也是dp,這裏用一個結構體subject型的數組Subject[16]儲存所有項目信息subject中包括項目名,最晚提交日期與消耗時間;用一個結構體nodeInformation型的數組dp[maxn]保存每個狀態的信息,nodeInformation包括現在狀態已經使用的天數,現在狀態的前狀態,前狀態到現在狀態所需提交的項目,現在狀態的最少扣分。

struct subject{
    string name;    //項目名
    int deadline;   //最後期限
    int costDays;   //耗費時間
}Subject[maxn];
struct nodeInformation{
    int costNow;    //現在狀態做項目已經使用的天數
    int now;    //前狀態到現在狀態所需提交的項目
    int pre;    //現在狀態的前狀態
    int subScore;   //現在狀態的最少扣分
}dp[maxn];

  之後遍歷所有狀態,開始時假設現在狀態瘋狂扣分扣無限分,之後遍歷所有項目作為前項目到現在狀態所提交的項目,每次遍歷看看能不能減少現在狀態的扣分,能的話就跟新dp數組信息。遍歷結束後用一個棧後進先出的特性從所有項目都提交的狀態向前尋找前狀態並入棧。之後出棧輸出項目名即可獲得提交的順序。

            int lastSubNow = endn;  //全部提交狀態
            while(lastSubNow){  //到全部未提交為止
                subName.push(dp[lastSubNow].now);   //入站前狀態
                lastSubNow = dp[lastSubNow].pre;
            }
            while(!subName.empty()){    //輸出提交順序
                cout << Subject[subName.top()].name << endl;
                subName.pop();
            }

  AC代碼

#include <bits/stdc++.h>
using namespace std;
const int maxn = 1 << 15;
const int inf = 0x7fffffff;
struct subject{
    string name;    //項目名
    int deadline;   //最後期限
    int costDays;   //耗費時間
}Subject[maxn];
struct nodeInformation{
    int costNow;    //現在狀態做項目已經使用的天數
    int now;    //前狀態到現在狀態所需提交的項目
    int pre;    //現在狀態的前狀態
    int subScore;   //現在狀態的最少扣分
}dp[maxn];
stack<int> subName; //記錄路徑
int main()
{
    int t;  //測試數量
    while(scanf("%d", &t) != EOF){
        while(t--){
            memset(dp, 0, sizeof(dp));
            int n;
            scanf("%d", &n);    //輸入項目數量
            for(int i = 0; i < n; i++){ //輸入每個項目
                cin >> Subject[i].name >> Subject[i].deadline >> Subject[i].costDays;
            }
            int endn = (1 << n) - 1;  //所有項目都提交
            for(int i = 1; i <= endn; i++){   //i所對應的2進制數代表每一個項目的完成情況,即i對應的二進制就是當前狀態
                dp[i].subScore = inf;   //初始化完成這種情況瘋狂扣分減無限分
                for(int j = n - 1; j >= 0; j--){
                    //遍歷所有項目,j為當前選擇作為前狀態提交項目的項目
                    int nowSubj = 1 << j;   //nowSubj代表只提交下標為j的項目的狀態
                    if(i & nowSubj){    //判斷i情況中下標為j的項目是否提交
                        //若已經提交進行如下操作
                        int past = i - nowSubj;//把i狀態的中代表j號項目的狀態改為沒有提交,就可以獲得i狀態提交j的前狀態past
                        int subScorej;
                        //subScorej代表past狀態提交j號項目的扣分
                        //它就等於past狀態已經消耗的天數加上j項目需要消耗的天數減去j號項目的要求的最晚提交日期。
                        subScorej = dp[past].costNow + Subject[j].costDays - Subject[j].deadline;
                        if(subScorej < 0){  //扣分不可能小於0,如果小於0就證明時間充足,扣分為0
                            subScorej = 0;
                        }
                        if(subScorej + dp[past].subScore < dp[i].subScore){ //如果從past狀態到現在狀態可以使i狀態當前的扣分減小
                            dp[i].subScore = subScorej + dp[past].subScore;
                            //更新i狀態的扣分
                            dp[i].costNow = dp[past].costNow + Subject[j].costDays;
                            //記錄以past為前狀態的i狀態消耗的天數
                            dp[i].now = j;
                            //前狀態past到現在狀態i並使其扣分最少所提交的項目為j項目
                            dp[i].pre = past;
                            //記錄i的前狀態為past
                        }
                    }
                }
            }
            printf("%d\n", dp[endn].subScore);
            //輸出提交全部項目的最少扣分(所有的項目都提交狀態最少扣分)
            int lastSubNow = endn;  //全部提交狀態
            while(lastSubNow){  //到全部未提交為止
                subName.push(dp[lastSubNow].now);   //入站前狀態
                lastSubNow = dp[lastSubNow].pre;
            }
            while(!subName.empty()){    //輸出提交順序
                cout << Subject[subName.top()].name << endl;
                subName.pop();
            }
        }
    }
    return 0;
}

HDU 1074 Doing Homework