单调队列优化的DP 洛谷 P1070 道路游戏

19 篇文章 0 订阅

单调队列优化的DP

单调队列整理链接

动态规划,我们经常会遇到转移的过程需要和前面 ( L , L + m ) (L,L+m) (L,L+m)的区间转移的操作

  • 若每次只需要 ( L , L + m ) (L,L+m) (L,L+m)的最大或最小值
  • m m m的大小固定
    那么我们就可以用单调队列来取 ( L , L + m ) (L,L+m) (L,L+m)的最值来优化动态规划

P1070 道路游戏

同类题链接

题意

在这里插入图片描述

分析

状态

本题动态规划的状态很难设计

  • 因为每次机器人用完后,能从任意节点购买机器人
    因此,节点的状态几乎没用

我们发现机器人个数也不是很重要
当我们以时间为状态时, d p [ i ] dp[i] dp[i]表示第 i i i个时刻,机器人已经走完的最大收益

状态转移

d p [ i ] = m a x ( d p [ i ] , d p [ i − 1 ] + w 1 , d p [ i − 2 ] + w 2 , ⋯   , d p [ i − p + 1 ] + w p − 1 ) dp[i]=max(dp[i],dp[i-1]+w_{1},dp[i-2]+w_{2},\cdots,dp[i-p+1]+w_{p-1}) dp[i]=max(dp[i],dp[i1]+w1,dp[i2]+w2,,dp[ip+1]+wp1)

单调队列优化

我们用前缀和来计算收益
t t t表示时间, j j j表示地点

t t t \ j j j123
1 a [ 1 ] [ 1 ] a[1] [1] a[1][1] a [ 1 ] [ 2 ] a[1][2] a[1][2] a [ 1 ] [ 3 ] a[1][3] a[1][3]
2 a [ 2 ] [ 1 ] a[2][1] a[2][1] a [ 2 ] [ 2 ] a[2][2] a[2][2] a [ 2 ] [ 3 ] a[2][3] a[2][3]
3 a [ 3 ] [ 1 ] a[3][1] a[3][1] a [ 3 ] [ 2 ] a[3][2] a[3][2] a [ 3 ] [ 3 ] a[3][3] a[3][3]

以下为前缀和

t t t \ j j j123123
1 a [ 1 ] [ 1 ] a[1] [1] a[1][1] a [ 1 ] [ 2 ] a[1][2] a[1][2] a [ 1 ] [ 3 ] a[1][3] a[1][3] a [ 1 ] [ 1 ] a[1] [1] a[1][1] a [ 1 ] [ 2 ] a[1][2] a[1][2] a [ 1 ] [ 3 ] a[1][3] a[1][3]
2 a [ 2 ] [ 1 ] a[2][1] a[2][1] a [ 1 ] [ 1 ] + a [ 2 ] [ 2 ] a[1][1]+a[2][2] a[1][1]+a[2][2] a [ 1 ] [ 2 ] + a [ 2 ] [ 3 ] a[1][2]+a[2][3] a[1][2]+a[2][3] a [ 1 ] [ 3 ] + a [ 2 ] [ 1 ] a[1][3]+a[2][1] a[1][3]+a[2][1] a [ 1 ] [ 1 ] + a [ 2 ] [ 2 ] a[1][1]+a[2][2] a[1][1]+a[2][2] a [ 1 ] [ 2 ] + a [ 2 ] [ 3 ] a[1][2]+a[2][3] a[1][2]+a[2][3]
3 a [ 3 ] [ 1 ] a[3][1] a[3][1] a [ 2 ] [ 1 ] + a [ 3 ] [ 2 ] a[2][1]+a[3][2] a[2][1]+a[3][2] a [ 1 ] [ 1 ] + a [ 2 ] [ 2 ] + a [ 3 ] [ 3 ] a[1][1]+a[2][2]+a[3][3] a[1][1]+a[2][2]+a[3][3] a [ 1 ] [ 2 ] + a [ 2 ] [ 3 ] + a [ 3 ] [ 1 ] a[1][2]+a[2][3]+a[3][1] a[1][2]+a[2][3]+a[3][1] a [ 1 ] [ 3 ] + a [ 2 ] [ 1 ] + a [ 3 ] [ 2 ] a[1][3]+a[2][1]+a[3][2] a[1][3]+a[2][1]+a[3][2] a [ 1 ] [ 1 ] + a [ 2 ] [ 2 ] + a [ 3 ] [ 3 ] a[1][1]+a[2][2]+a[3][3] a[1][1]+a[2][2]+a[3][3]
  • 关于对角线取前缀和

w j = − c o s t [ j − k ] + s u m [ t ] [ j ] − s u m [ t − k ] [ j − k ] w_{j}=-cost[j-k]+sum[t][j]-sum[t-k][j-k] wj=cost[jk]+sum[t][j]sum[tk][jk]
对于固定 i i i j j j,显然 − c o s t [ j − k ] − s u m [ t − k ] [ j − k ] -cost[j-k]-sum[t-k][j-k] cost[jk]sum[tk][jk]是连续的值,我们可以尝试用单调队列维护

t t t \ j j j123
1 d p [ 0 ] − c o s t [ 1 ] − s u m [ 0 ] [ 0 ] dp[0]-cost[1]-sum[0][0] dp[0]cost[1]sum[0][0] d p [ 0 ] − c o s t [ 2 ] − s u m [ 0 ] [ 1 ] dp[0]-cost[2]-sum[0][1] dp[0]cost[2]sum[0][1] d p [ 0 ] − c o s t [ 3 ] − s u m [ 0 ] [ 2 ] dp[0]-cost[3]-sum[0][2] dp[0]cost[3]sum[0][2]
2 d p [ 1 ] − c o s t [ 1 ] − s u m [ 1 ] [ 0 ] dp[1]-cost[1]-sum[1][0] dp[1]cost[1]sum[1][0] d p [ 1 ] − c o s t [ 2 ] − s u m [ 1 ] [ 1 ] dp[1]-cost[2]-sum[1][1] dp[1]cost[2]sum[1][1] d p [ 1 ] − c o s t [ 3 ] − s u m [ 1 ] [ 2 ] dp[1]-cost[3]-sum[1][2] dp[1]cost[3]sum[1][2]
3 d p [ 2 ] − c o s t [ 1 ] − s u m [ 2 ] [ 0 ] dp[2]-cost[1]-sum[2][0] dp[2]cost[1]sum[2][0] d p [ 2 ] − c o s t [ 2 ] − s u m [ 2 ] [ 1 ] dp[2]-cost[2]-sum[2][1] dp[2]cost[2]sum[2][1] d p [ 2 ] − c o s t [ 3 ] − s u m [ 2 ] [ 2 ] dp[2]-cost[3]-sum[2][2] dp[2]cost[3]sum[2][2]
  • 我们显然发现,对角线上元素符合 s u m [ t − k ] [ j − k ] sum[t-k][j-k] sum[tk][jk]

所以,我们需要维护一个对角线上的队列,对角线上元素 t − j t-j tj为定值

t t t \ j j j123
1 0 0 0 1 1 1 2 2 2
2 − 1 -1 1 0 0 0 1 1 1
3 − 2 -2 2 − 1 -1 1 0 0 0

我们以 t − j t-j tj为队列 i d id id的标号,维护队列

  • 我们每个时间点将每个 j j j对应的 d p [ t − 1 ] − c o s t [ j ] − s u m [ t − 1 ] [ j − 1 ] dp[t-1]-cost[j]-sum[t-1][j-1] dp[t1]cost[j]sum[t1][j1]加入相应的队列
  • 并且维护每个队列的长度 ≤ p \leq p p
void push(int t, int j) {
	int id = t-j+1000;
	int now = dp[t - 1] - cost[j] - sum[t - 1][j - 1];
	while (tail[id] >= head[id] && stack[id][tail[id]].first <= now)tail[id]--;
	stack[id][++tail[id]] = pii(now, t);
}
void getfront(int t,int id) {
	while (tail[id] >= head[id] && stack[id][head[id]].second < t - p + 1)head[id]++;
	ans[id] = stack[id][head[id]];
}
void Queue(int t) {
	for (int j = 1; j <= n; j++)
		push(t, j);
	for(int j=1-m+1000; j<=n-1+1000; j++)
		getfront(t,j);
}

状态转移

d p [ t ] = s u m [ t ] [ t − j + 1000 ] + a n s [ t − j + 1000 ] dp[t]=sum[t][t-j+1000]+ans[t-j+1000] dp[t]=sum[t][tj+1000]+ans[tj+1000]

for (int i = 1; i <= m; i++) {
		Queue(i);
		for (int j = 1 - m + th; j <= n - 1 + th; j++) {
			if (ans[j].second==0 || ans[j].second < i - p+1)continue;
			dp[i] = max(dp[i], ans[j].first + sum[i][i + j - th]);
		}
	}

AC代码

#include <iostream>
#include <cstdio>
#include <algorithm>
using namespace std;
typedef pair<int, int> pii;
const int maxn = 1005;
const int th = 1000;
const int inf = 0X3f3f3f3f;
int a[maxn][maxn], sum[maxn][maxn << 1], cost[maxn];
int dp[maxn];
int n, m, p;
void Read() {
	int x;
	scanf("%d%d%d",&n,&m,&p);
	for (int i = 1; i <= n; i++)
		for (int j = 1; j <= m; j++)
			scanf("%d",&a[j][i]);
	for (int i = 1; i <= m; i++) {
		for (int j = 1; j <= n; j++)
			sum[i][j] = sum[i - 1][j - 1] + a[i][j];
		for (int j = 1; j <= n; j++)
			sum[i][j + n] = sum[i - 1][j + n - 1] + a[i][j];
	}
	for (int i = 1; i <= n; i++)scanf("%d",cost[i]);
}
inline int getid(int t, int j) {
	return j - t + th;
}
pii stack[maxn << 1][maxn], ans[maxn << 1];
int head[maxn << 1], tail[maxn << 1];
void push(int t, int j) {
	int id = getid(t, j);
	int now = dp[t - 1] - cost[j] - sum[t - 1][j - 1];
	while (tail[id] >= head[id] && stack[id][tail[id]].first <= now)tail[id]--;
	stack[id][++tail[id]] = pii(now, t);
}
void getfront(int t,int id) {
	while (tail[id] >= head[id] && stack[id][head[id]].second < t - p + 1)head[id]++;
	ans[id] = stack[id][head[id]];
}
void Queue(int t) {
	for (int j = 1; j <= n; j++)
		push(t, j);
	for(int j=1-m+th; j<=n-1+th; j++)
		getfront(t,j);
}
int main() {
	Read();
	fill(dp, dp + 1 + m, -inf);
	dp[0] = 0;
	for (int i = 0; i <= th<<1; i++)head[i] = 1;
	for (int i = 1; i <= m; i++) {
		Queue(i);
		for (int j = 1 - m + th; j <= n - 1 + th; j++) {
			if (ans[j].second==0 || ans[j].second < i - p+1)continue;
			dp[i] = max(dp[i], ans[j].first + sum[i][i + j - th]);
		}
	}
	printf("%d\n", dp[m]);
}
  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值