道路游戏——题解

单调队列优化-道路游戏

[NOIP2009 普及组] 道路游戏

题目描述

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

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

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

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

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

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

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

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

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

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

输入格式

第一行 \(3\) 个正整数 \(n,m,p\),意义如题目所述。

接下来的 \(n\) 行,每行有 \(m\) 个正整数,每两个整数之间用一个空格隔开,其中第 \(i\) 行描。

述了 \(i\) 号马路上每个单位时间内出现的金币数量($1\le $ 金币数量 \(\le 100\)),即第 \(i\) 行的第 \(j\)\(1\le j\le m\))个数表示第 \(j\) 个单位时间内 \(i\) 号马路上出现的金币数量。

最后一行,有 \(n\) 个整数,每两个整数之间用一个空格隔开,其中第 \(i\) 个数表示在 \(i\) 号机器人工厂购买机器人需要花费的金币数量($1\le $ 金币数量 \(\le 100\))。

输出格式

共一行,包含 \(1\) 个整数,表示在 \(m\) 个单位时间内,扣除购买机器人花费的金币之后,小新最多能收集到多少金币。

样例 #1

样例输入 #1

2 3 2 
1 2 3 
2 3 4 
1 2

样例输出 #1

5

提示

对于 \(40\%\) 的数据,\(2\le n\le 40\)\(1\le m\le 40\)

对于 \(90\%\) 的数据,\(2\le n\le 200\)\(1\le m\le 200\)

对于 \(100\%\) 的数据,\(2\le n\le 1000\)\(1\le m\le 1000\)\(1\le p\le m\)

NOIP 2009 普及组 第四题

题解

首先本题是最优性问题,容易想到动态规划,那么我们来分析此题的动态规划做法

状态刻画

最朴素的想法:设\(dp[i,j,k]\)表示在时刻\(i\),上一个机器人在\(j\),走\(k\)步的最大收益

我们发现,在转移的时候,最后一个维度可以枚举消掉,故略去\(k\),继续由于我们可以从任意一个地方买机器人,于是机器人在哪里买的也不重要,省去第二维

最终得到\(dp[i]\)表示在时刻\(i\)所能得到的最大收益

状态转移

考虑转移。明显\(dp[i]\)的转移需要我们枚举上一个机器人在哪里出发,走了多少步,分别记为\(j,k\)

那么有:

\[dp[i]=\max_{0\le j< n}\left\lbrace\max_{1\le k\le q}\lbrace dp[i-k]+val[i-k,j,k]-cost[j-k]\rbrace \right\rbrace \]

其中\(cost\)表示买机器人所花的钱,而\(val\)表示一路上的收益

这里为了代码的方便,我们采取将点权下放到边权的形式,将边\((i,i+1)\)的权值记在\(i+1\)
为了用模运算简单处理环,我们需要将编号从零开始,所以读入时不会有任何改变

下一步,我们考虑如何将\(val\)降维,记\(a[i,j]\)表示在时刻\(i\)\(j\)条路的权值,记\(get(i)\)表示节点\(i\)在环上的位置(模运算)

\(val[i,j,k]\)展开有:

\[val[i,j,k]=\sum_{p=1}^ka[i+p,get(j+p)] \]

\(i\)\(i-k\)代替有

\[val[i-k,j,k]=\sum_{p=1}^ka[i-k+p,get(j+p)] \]

考虑进行转化,如果我们设\(f[i,j]\)表示前\(i\)个时间,最终走到\(j\)上的金币总和,那么有\(f[i,j]=\sum_{k=0}^{i}a[i-k,get(j-k)]\)

那么\(val[i-k,j,k]\)就等价于\(f[i,j]-f[i-k,get(j-k)]\),成功降维

接着,状态转移方程就可以改写成:

\[dp[i]=\max_{0\le j< n}\left \lbrace\max_{1\le k\le q}\lbrace dp[i-k]+f[i,j]-f[i-k,get(j-k)]-cost[j-k]\rbrace \right\rbrace \]

观察到\(f[i,j]\)只会用到一个矩形的对角线部分,于是预处理也只是\(O(nm)\)

需要注意的是我们在处理\(f\)的时候别忘记\(n\)对应的是\(0\)
到此,我们得到了一个复杂度为\(O(nmq)\)垃圾算法

转移优化

观察这个DP式子,如果我们把\(i,j\)看作定值,那么状态转移方程可以改写为

\[dp[i]=\max_{0\le j< n}\left\lbrace\max_{1\le k\le q}dp[i-k]-f[i-k,get(j-k)]-cost[j-k]+f[i,j]\right\rbrace \]

\(rec[i,j]=dp[i]-f[i,j]-cost[j]\)

状态转移方程就是\(dp[i]=\max_{0\le j< n}\left\lbrace\max_{1\le k\le q}\lbrace rec[i-k,j-k]+f[i][j]\rbrace \right\rbrace\)

考虑如何优化掉这个式子,这个样子非常像单调队列优化,只是我们如何定下单调队列优化的策略

考虑两个决策\((k_1,k_2),(k'_1,k'_2)\),如果\(k_1<k_1'并且rec[k_1,k_2]<rec[k_1',k_2']\),那么决策\((k_1,k_2)\)就是无用决策,可以排除

因为我们的决策涉及到了两个变量,就需要多个单调队列,并且我们需要考虑的是,一个决策\((k_1,k_2)\)对怎样的询问\((i,j)\)能够产生贡献

注意观察原来的\(DP\)方程\(dp[i]=\max_{0\le j< n}\left\lbrace \max_{1\le k\le q}dp[i-k]-f[i-k,get(j-k)]-cost[j-k]+f[i,j]\right\rbrace\),会发现\(rec\)部分有一个项\(f[i-k,get(j-k)]\),这个项意味着什么呢,意味着\(i-k-get(j-k)=get(i-j)\)(这一步成立是因为\(get\)函数本身基于模运算,可以支持交换律等)的决策,由此,所有满足\(k_1-k_2=get(i-j)\)的决策都是询问\((i,j)\)的候选决策

这启发我们按照\(get(i,j)\)来划分决策,分成不同的单调队列进行维护,最终就可以将复杂度优化至\(O(nm)\)

#include<bits/stdc++.h>
using namespace std;
#define N 1005
int n,m,p,dp[N],a[N][N],f[N][N],cost[N],l[N],r[N];
int pos(int x){return (x%n+n)%n;}
int get(int x,int y){return ((y-x)%n+n)%n;}
struct node{
	int pos,num;
}rec[N][N];
signed main(){
    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]); 
      	  if(i==n)a[j][0]=a[j][i];
	  }
    for(int i=1;i<=m;i++)
     	for(int j=0;j<n;j++)
        	f[i][j]=f[i-1][pos(j-1)]+a[i][j];
	for(int i=0;i<n;i++){
        scanf("%d",&cost[i]);
        rec[i][++r[i]].num=-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] && rec[id][l[id]].pos+p<i) l[id]++;
		    if(l[id]<=r[id]) 
		      dp[i]=max(dp[i],rec[id][l[id]].num+f[i][j]);
		}
		for(int j=0;j<n;j++){
			int id=get(i,j);
			while(l[id]<=r[id] && rec[id][r[id]].num<=dp[i]-f[i][j]-cost[j])
			  r[id]--;
			rec[id][++r[id]]=(node){i,dp[i]-f[i][j]-cost[j]};
		}
	}
	printf("%d\n",dp[m]);
	return 0;
}
posted @ 2022-11-30 22:53  spdarkle  阅读(122)  评论(0编辑  收藏  举报