P1070 道路游戏-动态规划,单调队列优化

版权声明:原创,勿转 https://blog.csdn.net/qq_42386465/article/details/82666745

小新正在玩一个简单的电脑游戏。

游戏中有一条环形马路,马路上有 nn个机器人工厂,两个相邻机器人工厂之间由一小段马路连接。小新以某个机器人工厂为起点,按顺时针顺序依次将这 nn个机器人工厂编号为1-n,因为马路是环形的,所以第nn个机器人工厂和第1个机器人工厂是由一段马路连接在一起的。小新将连接机器人工厂的这 n 段马路也编号为 1-n,并规定第ii段马路连接第 i 个机器人工厂和第 i+1个机器人工厂(1≤i≤n-1),第 nn段马路连接第 nn个机器人工厂和第1个机器人工厂。

游戏过程中,每个单位时间内,每段马路上都会出现一些金币,金币的数量会随着时间发生变化,即不同单位时间内同一段马路上出现的金币数量可能是不同的。小新需要机器人的帮助才能收集到马路上的金币。所需的机器人必须在机器人工厂用一些金币来购买,机器人一旦被购买,便会沿着环形马路按顺时针方向一直行走,在每个单位时间内行走一次,即从当前所在的机器人工厂到达相邻的下一个机器人工厂,并将经过的马路上的所有金币收集给小新,例如,小新在i(1≤i≤n)号机器人工厂购买了一个机器人,这个机器人会从 i号机器人工厂开始,顺时针在马路上行走,第一次行走会经过ii号马路,到达 i+1号机器人工厂(如果 i=n,机器人会到达第1个机器人工厂),并将i号马路上的所有金币收集给小新。 游戏中,环形马路上不能同时存在2个或者 2个以上的机器人,并且每个机器人最多能够在环形马路上行走p次。小新购买机器人的同时,需要给这个机器人设定行走次数,行走次数可以为 1~p 之间的任意整数。当马路上的机器人行走完规定的次数之后会自动消失,小新必须立刻在任意一个机器人工厂中购买一个新的机器人,并给新的机器人设定新的行走次数。

以下是游戏的一些补充说明:

  1. 游戏从小新第一次购买机器人开始计时。

  2. 购买机器人和设定机器人的行走次数是瞬间完成的,不需要花费时间。

  3. 购买机器人和机器人行走是两个独立的过程,机器人行走时不能购买机器人,购买完机器人并且设定机器人行走次数之后机器人才能行走。

  4. 在同一个机器人工厂购买机器人的花费是相同的,但是在不同机器人工厂购买机器人的花费不一定相同。

  5. 购买机器人花费的金币,在游戏结束时再从小新收集的金币中扣除,所以在游戏过程中小新不用担心因金币不足,无法购买机器人而导致游戏无法进行。也因为如此,游戏结束后,收集的金币数量可能为负。

现在已知每段马路上每个单位时间内出现的金币数量和在每个机器人工厂购买机器人需要的花费,请你告诉小新,经过 mm个单位时间后,扣除购买机器人的花费,小新最多能收集到多少金币。

https://www.luogu.org/problemnew/show/P1070

对于 100%的数据,2≤n≤1000,1≤m≤1000,1≤p≤m2≤n≤1000,1≤m≤1000,1≤p≤m。

NOIP 2009 普及组 第四题

此题曾推出一个dp[时间][机器人出发点][步数]的状态,后来发现出发点和步数并不需要在状态中体现。出发点可以是随意的一个,枚举每一个就行。步数的话也可以枚举,只要不超过p即可,可以用一个数组进行保存。

这样的话可以想到一个状态dp[i]表示i的时间得到的最大金币数:dp[i]=max(dp[i-k]+f[i][j]-f[i-k][j-k]-cost[i-k])。(这个复杂度O^3)

cost是每个边(点)买机器人的花费。

f[i][j]保存的是时间i位置j所延伸回去45度的各点金币之和,也就是小机器人从i这个点出发后走k步能得到的金币数,这里f数组保存的是前缀和

dp[i]=max(dp[i-k]+f[i][j]-f[i-k][j-k]-cost[i-k])

再来看这个方程,显然f[i][j]可以提出来,变成dp[i]=max(dp[i-k]-f[i-k][j-k]-cost[j-k])+f[i][j]。

这样看来我们只需要维护q[i][j]=dp[i]-f[i][j]-cost[j]的最大值就好,这已经能看得出来可以用单调队列优化。

那么dp[i]和cost[j]都是固定值,这里要维护f[i][j]在转移时的分布。

因为有可能有小机器人从n拐到1,这样图像位置就会发生改变。

考虑给单调队列编号,将队列(也就是每一根线)与location的交点作为其编号

int get(int x,int y)
{
	return ((y-x)%n+n)%n;
}

写个函数,就可以求出转移后新队列的编号。

还有两点要注意的地方

一是虚线所连的边仍然需要特判一下 二是为了确保拐弯后不出现问题,要把dp[i]=max(dp[i-k]-f[i-k][j-k]-cost[j-k])+f[i][j中的f[i][j]加上它失去的链的长度,维护一个add[i]数组。

#include<iostream>
#include<cstdio>
#include<cstring>
#include<cmath>
#include<algorithm>
#include<queue>
#include<stack>
#define N 1001
using namespace std;
int inline read()
{
	int x=0,f=1;char ch=getchar();
	while(ch<'0'||ch>'9'){if(ch=='-')f=-1;ch=getchar();}
	while(ch>='0'&&ch<='9'){x=x*10+ch-'0';ch=getchar();}
	return x*f;
}
int n,m,p;
int f[N][N],dp[N],loc[N][N],q[N][N],add[N],l[N],r[N],cost[N];
int get(int x,int y)
{
	return ((y-x)%n+n)%n;
}
int main()
{
	n=read();m=read();p=read();
	for(int i=1;i<=n;i++){
		for(int j=1;j<=m;j++){
			f[j][i]=read();
			f[j][i]+=f[j-1][i-1];
		}
	}
	for(int i=0;i<n;i++){
		cost[i]=read();
		q[i][++r[i]]=-cost[i];
		l[i]++;
	}
	memset(dp,-0x3f,sizeof(dp));
	dp[0]=0;
	for(int i=1;i<=m;i++){
		for(int j=0;j<n;j++){
			int id=get(i,j);
			while(l[id]<=r[id]&&loc[id][l[id]]+p<i) l[id]++;
			if(!j) add[id]+=f[i][n];
			if(l[id]<=r[id]){
				dp[i]=max(dp[i],q[id][l[id]]+add[id]+f[i][j]);
			}
		}
		for(int j=0;j<n;j++){
			int id=get(i,j);
			int tmp=dp[i]-add[id]-cost[j]-f[i][j];
			while(l[id]<=r[id]&&q[id][r[id]]<tmp) r[id]--;
			loc[id][++r[id]]=i;
			q[id][r[id]]=tmp;
		}
	}
	cout<<dp[m];
	return 0;
}

猜你喜欢

转载自blog.csdn.net/qq_42386465/article/details/82666745