HDU 1074 (狀態壓縮DP)

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

題意:給出n(n <= 15)個科目的作業,每個作業分別有截止日期、完成天數,如果超出了截止日期還沒完成作業,則會被扣掉 (完成日期 - 截止日期) 的學分,問怎樣安排各科作業的先後順序,使得被扣除的學分最少,如果有多種安排方案,按字典序輸出。(數據保證輸入時,科目名是按照字典序的)

  由數據規模可以知道可以用狀態壓縮DP來解決。一般的DP題目都可以用搜索和遞推兩種方法解決,這道題來說,遞推思路會比較直觀,而遞推的時間效率高很多!

  用搜索來解決,主要要注意兩個地方,一是狀態的轉移,二是剪枝。由狀態0開始進行DFS,每次添加一個當前還沒有安排的科目,然後遞歸進入下一層,當層數等於科目數的時候,即表示所有科目的順序已經安排完畢!如當n = 3時,當前遞歸的狀態的壓縮表示是010,可以遞歸到的下一層是010 + 1(新安排第一個科目) 或者010 + 100(新安排第三個科目),如此類推。遞歸的時候,要注意記錄當前新加入的科目,這樣就完成了記錄路徑的工作,相當方便,還要注意當前日期的變換、扣除學分的累加等等。最後要注意的就是當前狀態被扣除的學分大於等於以前搜索過的情況時,剪枝!

  用遞推的方法,要注意三個地方,一是狀態的轉移(這裏比上一種方法複雜),二是記錄路徑(記錄路徑和最終回溯都比上一種方法麻煩多了),三是按字典序輸出答案。注意到,每一個狀態都是由一種or多種(前一個狀態 + 新安排的科目)轉移得到的,如當前狀態的壓縮狀態表示爲111,則有可能是由110 + 1(新加入第一個科目),或者101 + 10(新加入第二個科目),或者001 + 100(新安排第三個科目)轉移得到,所以只需取上述情況中被扣學分最少的狀態來構造當前狀態就可以了(當然還要注意當被扣學分相同時,按照字典序來安排科目,但這個只需要一個小技巧即可解決)。知道狀態轉移的方法後,依次將1 ~ (1<<n)-1的狀態當成當前狀態遍歷,
 首先枚舉一下當前狀態可以由哪些前驅狀態得到,然後取所有前驅狀態的最小值即可,因爲是按順序計算狀態的,所以前驅狀態必定是在之前已經計算好了,直接提取前驅狀態即可,具體參照代碼中past -> now的過程

代碼:

//搜索  296MS
#include<iostream>
#include<cstring>
#include<cstdio>
#include<string>
#include<algorithm>
#define inf 1e9
using namespace std;
 
struct Data {
    string name;
    int deadline, days;
    int vis, mark;
}sub[20];
 
int minus_score;
int DP[40000];            //記錄狀態的數組
 
bool cmp1(Data a, Data b) { return a.name < b.name; }  // 先按字典序排序
 
bool cmp2(Data a, Data b) { return a.mark < b.mark; }  // 輸出時按課程優先級排序
 
void DFS(int n, int depth, int score, int date, int now) {
    // n是科目數,depth是遞歸深度,score是當前減少的學分,
    // date是當前日期,now是當前狀態的壓縮表示
    int i;
    if(n == depth) {
        if(score < minus_score) {
            minus_score = score;
            for(i = 0; i < n; ++i) sub[i].mark = sub[i].vis;
        }
        return;
    }
    if(DP[now] > score) DP[now] = score;    // *注意這個剪枝
    else return;
    for(i = 0; i < n; ++i) {
        if(sub[i].vis) continue;
        sub[i].vis = depth + 1;
        int temp = 0;
        if(sub[i].days + date > sub[i].deadline)
        //完成時間超出了截止時間,累計扣除的學分
            temp = sub[i].days + date - sub[i].deadline;
        DFS(n, depth + 1, score + temp, date + sub[i].days, now + (1<<i));
        //     深度+1,累計扣除的學分,累計日期,狀態轉移
        sub[i].vis = 0;
    }
}
 
int main() {
    int T;
    cin>>T;
    while(T--){
        int num;
        cin>>num;
        int i;
        for(i = 0; i < num; ++i) {
            cin>>sub[i].name>>sub[i].deadline>>sub[i].days;
            sub[i].vis = 0;
        }
        for(i = 0; i < 40000; ++i) DP[i] = inf;
        minus_score = inf;
        //sort(sub, sub + num, cmp1);
        // 如果題目沒保證按字典序輸入,則需要多一次排序
        DFS(num, 0, 0, 0, 0);
        sort(sub, sub + num, cmp2);
        cout<<minus_score<<endl;
        for(i = 0; i < num; ++i)
            cout<<sub[i].name<<endl;
    }
    return 0;
}

//遞推  15MS
#include<iostream>
#include<cstring>
#include<cstdio>
#include<string>
#include<stack>
#define inf 1e9
using namespace std;
 
struct Data1 {
    string name;
    int deadline, days;
}sub[20];
 
struct Data2 {
    int date, score, pos, last;
    //當前狀態下的日期、扣除的學分,轉移時新增的科目、轉移前的狀態
}DP[40000];
 
int main() {
    int T;
    cin>>T;
    while(T--) {
        int n, i, j;
        cin>>n;
        for(i = 0; i < n; ++i)
            cin>>sub[i].name>>sub[i].deadline>>sub[i].days;
        DP[0].date = 0, DP[0].score = 0;
        for(i = 1; i < (1<<n); ++i) {
        // 轉移後的狀態
            DP[i].score = inf;
            for(j = n-1; j >= 0; --j) {
            //這裏逆序遍歷狀態轉移時添加的科目,可以保證相同score時,按字典序輸出
                if( i & (1<<j) ) {      //符合條件,狀態則由past轉移到now
                    int now = i;
                    int past = i - (1<<j);
                    int temp = 0;
                    if(DP[past].date + sub[j].days > sub[j].deadline)
                        temp = DP[past].date + sub[j].days - sub[j].deadline;
                    if(DP[past].score + temp < DP[now].score) {
                        DP[now].score = temp + DP[past].score;
                        DP[now].date = DP[past].date + sub[j].days;
                        DP[now].pos = j;       // 記錄轉移的“路徑”
                        DP[now].last = past;
                    }
                    //cout<<past<<": "<<DP[past].date<<" "<<DP[past].score<<endl;
                    //cout<<i<<": "<<DP[i].date<<" "<<DP[i].score<<endl;
                }
            }
        }
        int Max = (1<<n) - 1;
        cout<<DP[Max].score<<endl;
        stack<int> sta;
        int p = Max;
        while(p != 0) {
        // 回溯路徑
            sta.push(DP[p].pos);
            p = DP[p].last;
        }
        while(!sta.empty()) {
            int top = sta.top();
            sta.pop();
            cout<<sub[top].name<<endl;
        }
    }
    return 0;
}


發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章