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;
}