Codeforces Round #90 C题(三维递推DP)

http://www.codeforces.com/contest/119/problem/C

  题意:一个学年有N天,每天必须从M个科目中挑选出一个。每个科目有3个属性,ai、bi、ci,表示第i个科目的作业量在[ai, bi]中,该科目的难度系数为ci。安排科目时,必须一天比一天的难度系数大除第一天外,每一天的科目还要安排作业量,除第一天外,每一天的作业量必须符合now = past+k or now = past*k。问能否按要求安排完N天的作业,如果能则输出总作业量最大的安排方案,输出格式见Sample。
  这题用递推思路比较清晰,dp[i][j][k]表示第j个科目作业量为k的状态,安排在第i天后的总作业量,但作业量的确定值太大,而作业量的范围<=100,所以第三维只需要保存作业量最小值的增量即可。
  做法是将科目先按ci排个序、记录原来的位置,再初始化第一天的安排情况,然后一个循环不断加深天数,循环里依次更新能够转移的状态(暴力枚举),并顺便记录下前驱状态方便回溯路径。最后的工作就是找出第n天是否存在作业量为正的状态,不存在则没有合法方案,存在则选出最大值,回溯路径、打印。
  一开始,我的想法是只开二维,没有多开一维空间来记录当前安排到的天数,结果一直在“保证能安排的天数尽可能多”和“保证当前安排的总作业量最大”之间纠结(缺乏DP的触觉),后来看了队友Band的代码,才恍然大悟,只要多开一维来记录安排天数的深度,则DP时只需关注总作业量最大就可以了……另外代码中混用了int 和 __int64,不放心的话可以全部改成__int64 or long long ~~

// 90 MS
#include<iostream>
#include<cstring>
#include<cstdio>
#include<algorithm>
#include<stack>
using namespace std;
 
struct Data {
    __int64 a, b, c;
    int len, pos;    // len是作业量的最大增量,pos记录当前科目的原始位置
}o[100];
__int64 dp[110][110][110];
pair<int, __int64> pre[110][110][110];
// 分别记录前驱状态的科目、作业量增量
 
int f[110];
int n, m, k;
 
bool cmp(Data a, Data b) { return a.c < b.c; }
 
int main() {
    while(cin>>n>>m>>k) {
        memset(dp, 0, sizeof(dp));
        for(int i = 1; i <= m; ++i) {
            cin>>o[i].a>>o[i].b>>o[i].c;
            o[i].len = o[i].b - o[i].a;
            o[i].pos = i;
        }
        sort(o+1, o+m+1, cmp);
        for(int i = 1; i <= m; ++i) {
            for(int j = 1; j <= m; ++j)
                if(o[j].pos == i) {
                    f[j] = i; break;
                }
        }
        //------------------------------初始化
        for(int i = 1; i <= m; ++i) {
            for(int j = 0; j <= o[i].len; ++j) {
                dp[1][i][j] = o[i].a + j;
            }
        }
        // 核心DP递推部分
        for(int dep = 1; dep < n; ++dep) {
        //  当前已经安排了科目的天数
            for(int i = dep+1; i <= m; ++i) {
            // 枚举有可能更新状态的科目
                for(int j = 1; j < i; ++j) {
                // 枚举难度系数更小的科目,尝试更新第i个科目的状态
                    if(o[i].c == o[j].c) continue;
                    for(int jj = 0; jj <= o[j].len; ++jj) {
                    // 枚举第j个科目的作业量为aj + jj时的状态
                        if(dp[dep][j][jj] == 0) continue;
                        __int64 t = o[j].a + jj;
                        if(o[i].a <= t+k && t+k <= o[i].b) {
                            __int64 u = t+k - o[i].a;
                            if(dp[dep][j][jj] + o[i].a + u > dp[dep+1][i][u]) {
                                dp[dep+1][i][u] = dp[dep][j][jj] + o[i].a + u;
                                pre[dep+1][i][u].first = j;
                                pre[dep+1][i][u].second = jj;
                            }
                        }
                        // 两种转移的情况、记录路径
                        if(o[i].a <= t*k && t*k <= o[i].b) {
                            __int64 u = t*k - o[i].a;
                            if(dp[dep][j][jj] + o[i].a + u > dp[dep+1][i][u]) {
                                dp[dep+1][i][u] = dp[dep][j][jj] + o[i].a + u;
                                pre[dep+1][i][u].first = j;
                                pre[dep+1][i][u].second = jj;
                            }
                        }
                    }
                }
            }
        }
        //-------------------------------------找出作业量最大的状态
        int x, y;
        __int64 Max = 0;
        for(int i = 1; i <= m; ++i) {
            for(int j = 0; j <= o[i].len; ++j) {
                //cout<<dp[n][i][j]<<" ";
                if(dp[n][i][j] > Max) {
                    Max = dp[n][i][j];
                    x = i; y = j;
                }
            }
           //cout<<endl;
        }
        //------------------------------判断是否有合适方案,回溯输出
        if(!Max) {
            cout<<"NO"<<endl;
            continue;
        }
        cout<<"YES"<<endl;
        stack< pair<int, __int64> > sta;
        for(int i = n; i >= 1; --i) {
            int t1 = x, t2 = y;
            x = pre[i][t1][t2].first;
            y = pre[i][t1][t2].second;
            if(i == 1)
                sta.push(make_pair(t1, dp[i][t1][t2]));
            else
                sta.push(make_pair(t1, dp[i][t1][t2] - dp[i-1][x][y]));
        }
        while(!sta.empty()) {
            cout<<f[sta.top().first]<<" "<<sta.top().second<<endl;
            sta.pop();
        }
    }
    return 0;
}


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