UVA 1427 Parade

本文介绍了一种使用单调队列优化动态规划(DP)的方法,解决了一个路径选择问题,目标是最大化路径上的值之和,同时确保在每条横向上花费的时间不超过限定值。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

单调队列优化DP

参考原文

题意: F城由n+1个横向路和m+1个竖向路组成。你的任务是从最南边的路走到最北边的路,使得走过的路上的高兴值的和最大(高兴值可能为负数)。同一段路最多只能走一次,且不能从北往南走。另外,在每条横向路上所花的时间不能超过k。1<=n<=100,1<=m<=10000,0<=k<=3000000。

解法: 很容易想到设dp[i][j]到第i行第j列那个点的最大值,设L[i][j]为从第i行的左边走到i的最大值,R[i][j]为从第i行的右边走到i的最大值,那么有如下转移:

dp[i][j] = max(L[i][j],R[i][j],dp[i+1][j])。注意边界问题。

乍看之下问题貌似解决了。但是呢,如果对于每个点都要对他的同一行扫一遍来计算他的L和R,要m的时间,总共约n*m个点,复杂度O(n*m^2),m范围有1W,不超时直播吃键盘- -!。

所以呢,考虑优化一下L和R的计算: L[i][j] = min(dp[i+1][k] + val[k~j]),0<=k<j。

如果happy值val处理成前缀和的形式,会不会更快一点呢,那么方程变为: L[i][j] = min(dp[i+1][k] + SumVal[0~j] - SumVal[0~k])。

光是前缀和貌似效果还不够好,那么方程再变个型: L[i][j] = min(dp[i+1][k] - SumVal[0~k]) + SumVal[0~j]。

注意min括号里的东西,dp[i+1][k]-SumVal[0~k],这个式子,跟上上上面的方程的区别在于,他只和k有关,k<j,那么,集合{0~j} - 集合{0~j-1} = {j},明显的递推关系!那么在知道前向的结果的情况下,计算出这一项只需要O(1)的时间!因此时间上可以优化到O(n*m)。

考虑对于横向路的时间不能超过k这个限制的处理。我们需要的值是j节点左端不超过k时间范围内的值,而每次递推时增加的值在时间上都是稳定最小的,天然的时间递减顺序!

考虑插入时间为k的这个值时,他必定是在最右端的,对于他左端比他小的值,可以直接忽略了(因为更小的范围,值还比你大,我要你做什么),而左端比他大的值,若时间不超过范围,则不能删去,因为可能会用到。这样,就构成了一个由左到右,在时间上递减,在happy值上也递减的序列,要维护这样一个序列,要支持两端删除,右端插入,显然是双端队列这个数据结构了。每次要取最大值时从最左端取值,时间O(1),每个值最多进队一次,出队一次,处理一行的时间为O(m),so nice!

总结: 单调队列优化的DP具有这样的性质: 有dp[i] = min(h[i]) + g[i],他的取最值部分h[i]可以被划分成与i值无关的,可递推的式子,这样的方程用单调队列能够将复杂度降一个幂次。

对于这道题,在POJ上貌似用C++光是读入就已经超时了。所以用G++和快速读入优化了一下,一不小心刷到第一了- -

/* **********************************************
Author      : Nero
Created Time: 2013-8-30 15:17:38
Problem id  : UVA 1427
Problem Name: Parade
*********************************************** */

#include <stdio.h>
#include <string.h>
#include <algorithm>
#include <ctype.h>
using namespace std;
#define REP(i,a,b) for(int i=(a); i<(int)(b); i++)
#define clr(a,b) memset(a,b,sizeof(a))

const int INF = 0x3f3f3f3f;                                                                                                                                   
int mat[110][10100];
int path[110][10100];
int n,m,K;
struct Q {
    int val,time;
}q[10100];
int qf,qe;
int dp[10100];
int temp[10100];

inline int GetInt() {
    char c;
    do {
        c = getchar();
    }while(!isdigit(c) && c != '-');
    int ret = 0;
    int sigma = 1;
    if(c == '-') sigma = -1;
    else ret = c - '0';
    while(isdigit(c = getchar())) ret = ret * 10 + c - '0';
    return ret*sigma;
}

int main() {
    while(~scanf("%d%d%d", &n, &m, &K), n || m || K) {
        clr(dp,0);
        for(int i = 0; i <= n; i ++) path[i][0] = path[i][m+1] = 0;
        for(int i = 0; i <= n; i ++) mat[i][0] = mat[i][m+1] = 0;
        for(int i = 0; i <= n; i ++) {
            for(int j = 1; j <= m; j ++) {
                mat[i][j] = GetInt();
                mat[i][j] += mat[i][j-1];
            }
        }
        for(int i = 0; i <= n; i ++) {
            for(int j = 1; j <= m; j ++) {
                path[i][j] = GetInt();
                path[i][j] += path[i][j-1];
            }
        }
        for(int i = n; i >= 0; i --) {
            for(int j = 0; j <= m; j ++) temp[j] = dp[j];
            qf = 0;
            qe = -1;
            for(int j = 1; j <= m; j ++) {
                int tv = temp[j-1] - mat[i][j-1];
                while(qf <= qe && q[qe].val <= tv) qe --;
                q[++qe].val = tv;
                q[qe].time = path[i][j-1];
                while(qf <= qe && path[i][j]-q[qf].time > K) qf ++;
                if(qf <= qe) dp[j] = max(dp[j], q[qf].val+mat[i][j]);
            }

            qf = 0;
            qe = -1;
            for(int j = m-1 ; j >= 0; j --) {
                int tv = temp[j+1] + mat[i][j+1];
                while(qf <= qe && q[qe].val <= tv) qe --;
                q[++qe].val = tv;
                q[qe].time = path[i][j+1];
                while(qf <= qe && q[qf].time - path[i][j] > K) qf ++;
                if(qf <= qe) dp[j] = max(dp[j], q[qf].val-mat[i][j]);
            }
        }
        
        int maxn = -(~0u>>1);
        for(int i = 0; i <= m; i ++) {
            maxn = max(maxn, dp[i]);
        }
        printf("%d\n", maxn);
    }
    return 0;
}


### 自动生成 Parade 相关代码或配置 为了在 IntelliJ IDEA 中自动创建 `@Parade` 注解或者相关配置,可以考虑以下几个方面: 对于注解的自动生成,在 Java 开发环境中通常依赖于 IDE 的插件支持或者是通过特定框架提供的功能来实现。如果希望为项目添加一个新的注解 `@Parade` 并让其能够被识别以及触发某些行为,则可能涉及到定义该注解本身及其处理器。 当提到自动化生成与某个特定标签关联的文件时,这往往意味着要利用构建工具或是开发环境所提供的模板机制。例如,在 Maven 或 Gradle 构建脚本里指定资源过滤规则;而在编辑器端则可以通过 Live Templates 功能快速输入重复性的代码片段[^1]。 另外一种方式就是借助 Lombok 这样的库简化样板代码编写工作,不过需要注意的是这里讨论的对象是 `@Parade` 而不是常见的 Lombok 提供的那种类型的注解。因此,除非已经存在类似的第三方库实现了这一需求,否则还是得自己动手完成这部分工作的大部分内容。 针对 IntelliJ IDEA 特有的特性来说,除了上述方法外还可以探索如下选项: - **Live Templates**: 创建自定义 live template 来加速常用模式下 `@Parade` 使用场景中的编码效率。 - **Inspections and Quick Fixes**: 如果有频繁修改的需求,那么设置 Inspection 和对应的 Quick Fix 可能会有所帮助。 - **File/Code Templates**: 修改全局范围内的 File Template 或者 Code Snippet 以便每次新建类的时候都能方便地引入所需的注解声明。 最后值得注意的一点是在实际操作之前应当确认所使用的版本是否兼容这些特性和扩展,并查阅官方文档获取最新指导说明。 ```java // 自定义注解示例 import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; @Retention(RetentionPolicy.RUNTIME) @Target(ElementType.TYPE) public @interface Parade { String value() default ""; } ```
评论 5
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值