Codeforces Ilya and Roads
http://codeforces.com/contest/313/problem/D
区间DP
很好的一道题目,是上周的比赛的题目了现在才补上来
题意:给一个总区间,下面m个小区间,每个小区间有对应的花费,要求用这些小区间去覆盖总区间(允许有重叠),要求覆盖k个单元(不一定连续,只要k个),问最小花费是多少
很典型的区间dp问题,不过数据很大,要想想怎么处理
留意到,总区间长度只有300,但是可供选择的小区间的数目多达10^5个,所以可知很多区间是可以去掉的,相同的区间,我们当然只保留花费最小的,但是除此之外还能怎样再减少小区间的数目呢?
while(m--) { int x,y; ll c; cin >> x >> y >> c; for(int i=x; i<=y; i++) cost[x][i] = min(cost[x][i],c); }
这样做的原因,在相同dp的原理后就会明白
但是要注意一点,对于一个小区间【x,y】,只能更新cost[x][x] , cost[x][x+1] , cost[x][x+2] , cost[x][x+3] …… cost[x][y],去区间的左端不能更改,即不能cost[x+k][]这样的,否则是错误的做法
定义状态:dp[i][j] , 表示从1到i这段连续的区间内,覆盖了k个单元的最小花费
状态转移为
首先初始化 dp[i][j] = dp[i-1][j] , 继承前面i-1个单元的成果
然后 dp[i][j] = min(dp[i][j] , dp[i-k][j-k] + cost[i-k+1][i]);
用文字来表达也很直白,覆盖后面的一段连续的区间 [i-k+1][i] (长度也就是k),那么就已经覆盖了k个,目标是覆盖j个,那么还剩下j-k个,这j-k将在哪里被覆盖,就是在[1,i-k]里面被覆盖
即:【1,i】的区间分成两段[1,i-k] [i-k+1 , i] , 前面那段,覆盖了j-k个,不一定是连续的,后面那段,覆盖了k个,必定是连续的。 费用就是两段的费用和
所以能理解为什么前面计算cost数组的时候要哪样计算了吧?
#include <iostream> #include <cstdio> #include <cstring> using namespace std; #define N 100010 #define M 310 #define INF 300000000010 typedef long long ll; ll cost[M][M]; ll dp[M][M]; int main() { int n,m,s; cin >> n >> m >> s; for(int i=0; i<=n; i++) for(int j=0; j<=n; j++) dp[i][j] = cost[i][j] = INF; while(m--) { int x,y; ll c; cin >> x >> y >> c; for(int i=x; i<=y; i++) cost[x][i] = min(cost[x][i],c); } dp[0][0] = 0; for(int i=1; i<=n; i++) for(int j=0; j<=i; j++) { dp[i][j] = dp[i-1][j]; for(int k=1; k<=j; k++) dp[i][j] = min(dp[i][j] , dp[i-k][j-k] + cost[i-k+1][i]); } if(dp[n][s] >= INF) dp[n][s] = -1; cout << dp[n][s] << endl; return 0; }