题目链接:http://acm.hdu.edu.cn/showproblem.php?pid=5543
题意:往长为L的线段上覆盖线段,要求:要么这些线段都在L的线段上,要么有不超过自身长度一半的部分在线段外面,最多有两条这样的线段(在两头)。
dp(i,j,k)表示前i个线段覆盖在长度为j的线段上,期中有k个线段不完全在这个线段上的最大价值。考虑线段长度的奇偶问题,所以事先把L和其他线段长度乘2,以便操作。所以枚举所有线段,一般情况,就是01背包的问题,dp(i,j,k)=max(dp(i,j,k), dp(i-1,j-w(i),k)+v(i))。当枚举到k不是0的时候,还需要更新两端放的情况:dp(i,j,k)=max(dp(i,j,k),dp(i-1,j-w(i)/2,k-1)+v(i))。直接取w(i)/2是没有关系的,因为我们在这里希望可以贪心地尽可能地少占用当前L上的长度。
还有一个trick:L=1的时候这么搞。这时L当成一个支点,只能放一个。所以要提前处理所有线段的最大价值。
1 #include <bits/stdc++.h> 2 using namespace std; 3 4 typedef long long LL; 5 const int maxn = 5010; 6 int w[maxn], v[maxn]; 7 int n, L; 8 LL dp[maxn][3]; 9 LL ret; 10 11 int main() { 12 freopen("in", "r", stdin); 13 int T, _ = 1; 14 scanf("%d", &T); 15 while(T--) { 16 scanf("%d %d", &n, &L); 17 memset(dp, 0, sizeof(dp)); 18 ret = 0; L <<= 1; 19 for(int i = 1; i <= n; i++) { 20 scanf("%d %d", &w[i], &v[i]); 21 w[i] <<= 1; 22 ret = max(ret, (LL)v[i]); 23 } 24 for(int i = 1; i <= n; i++) { 25 for(int j = L; j >= w[i]/2; j--) { 26 for(int k = 0; k <= 2; k++) { 27 if(j >= w[i]) dp[j][k] = max(dp[j][k], dp[j-w[i]][k]+v[i]); 28 if(k) dp[j][k] = max(dp[j][k], dp[j-w[i]/2][k-1]+v[i]); 29 ret = max(ret, dp[j][k]); 30 } 31 } 32 } 33 printf("Case #%d: %lld ", _++, ret); 34 } 35 return 0; 36 }