LA 4327 Parade(单调队列优化dp)

题目链接:

题目大意(摘自刘汝佳<<算法竞赛入门经典--训练指南>>):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
", ans);
    }
    return 0;
}
原文地址:https://www.cnblogs.com/beisong/p/4755797.html