题目链接:
题目大意(摘自刘汝佳<<算法竞赛入门经典--训练指南>>):F城是由n+1条横向路和m+1条竖向路组成。你的任务是从最南边的路走到最北边的路,使得走过的路上的高兴值和最大(注意,一段路上的高兴值可能是负数)。同一段路不能经过两次,且不能从北往南走,另外,在每条横向路上所花的时间不能超过k。求从南到北走完可以获得的最大高兴值。
分析:用dp[i][j]表示到达编号为(i,j)的路口时所能得到的最大高兴值,则dp[i][j] = max{ dp[i+1][j]; dp[i+1][k] + s[i+1][j]-s[i+1][k], (L[i][j] <= k < j); dp[i+1][k] + s[i+1][k]-s[i+1][j], (j <= k < R[i][j]) };
其中,s[i][j] 表示第i行从第0个路口到第j个路口的高兴值之和,L[i][j]是同一行上能够走到(i,j)位置的最左位置,R[i][j]是同一行上能够走到(i,j)位置的最右位置,这三个量都可以预处理出来。
上述dp的复杂度是O(n^3)的,需要优化,考虑dp[i][j] = max(dp[i+1][k] + s[i+1][j]-s[i+1][k]),i,j固定时,s[i+1][j]为常量,不妨设s[i+1][j] = P, 且令f[k] = dp[i+1][k] - s[i+1][k],则dp[i][j] = max(f[k]) + P,此时就可以用单调队列来维护了,对于每行,从左到右扫描,并维护一个递减的单调对列。具体参看代码。
#include <cstdio> #include <algorithm> using namespace std; #define N 104 #define M 10004 int L[N][M], R[N][M]; int v[N][M], t[N][M]; int n, m, k; int Q[M]; int f[M], sum[N][M], dp[N][M]; int main() { while(~scanf("%d %d %d", &n, &m, &k), n||m||k) { for(int i = 1; i <= n+1; i++) for(int j = 0; j < m; j++) scanf("%d", &v[i][j]); for(int i = 1; i <= n+1; i++) for(int j = 0; j < m; j++) scanf("%d", &t[i][j]); for(int i = 1; i <= n+1; i++) { sum[i][0] = 0; for(int j = 1; j <= m; j++) sum[i][j] = sum[i][j-1] + v[i][j-1]; } for(int i = 1; i <= n+1; i++) { L[i][0] = 0, R[i][m] = m; int cur = 0, id = 0; for(int j = 1; j <= m; j++){ cur += t[i][j-1]; while(cur > k) cur -= t[i][id++]; L[i][j] = id; } cur = 0, id = m-1; for(int j = m-1; j >= 0; j--){ cur += t[i][j]; while(cur > k) cur -= t[i][id--]; R[i][j] = id+1; } } for(int i = 0; i < m+1; i++) dp[n+1][i] = 0; for(int i = n; i >= 0; i--) { int head = 0, rear = 0; for(int j = 0; j < m+1; j++) { f[j] = dp[i+1][j] - sum[i+1][j]; while(rear < head && Q[rear] < L[i+1][j]) rear++; while(head > rear && f[j] >= f[Q[head-1]]) head--; Q[head++] = j; dp[i][j] = max(dp[i+1][j], sum[i+1][j]+f[Q[rear]]); } head = 0, rear = 0; for(int j = m; j >= 0; j--) { f[j] = dp[i+1][j] + sum[i+1][j]; while(rear < head && Q[rear] > R[i+1][j]) rear++; while(head > rear && f[j] >= f[Q[head-1]]) head--; Q[head++] = j; dp[i][j] = max(dp[i][j], f[Q[rear]]-sum[i+1][j]); } } int ans = 0; for(int i = 0 ; i < m+1; i++) ans = max(ans, dp[0][i]); printf("%d\n", ans); } return 0; }