cqyz oj |【综合训练】百层游戏 P2729 | DP动态规划 | 滑动窗口

Description

小沐最近迷上一款称为“100层”的游戏,其规则如下:
1、最开始角色在第一层某个房间。
2、每一层包含m个房间,在同一层上,角色可以向一个方向走,即要么向左,要么向右,但最多经过连续T个房间后到达同层一个房间(这里你可理解为连续经过T+1个房间)。
3、在每一层也可直接走到上一层,即从第i层的第j个房间,可以直接上到第i+1层的第j个房间。
4、角色每经过一个房间和到达一个房间,会获得一定的分数。所以角色最终获得的分数是它经过房间的分数之和。
5、游戏的目标是角色从第1层走到最高层要获取最高的分数。

Input

  第一行包含四个整数:n,m,x,T,表示游戏共有n层,每层有m个房间,角色最初在第1层的第x个房间,角色在同一层上向左或向右最多经过T个房间。  接下来的n行,每行包含m个整数,其中第i+1行的第j个整数,表示第i 层第j个房间的分数。

Output

  一个整数,表示获取的最高分。

Sample Input 1

3 3 2 1
7 8 1
4 5 6
1 2 3

Sample Output 1

29

Hint

1<=N<=100,1<=M<=10000,1<=X,T<=M,-500<=房间分数<=500


一看到这个题就想起了另一个题目cqyz oj|P1419 HB办证,但是多了一个限制条件——每一层向左向右走的最大房间数量。
由那个题的思路,定义状态函数\(dp(i,j)\)表示第\(i\)层第\(j\)个房间得分的最大值,设\(a[i][j]\)为第\(i\)层第\(j\)个房间的得分,可以先得到一个粗略的思路:

\[dp(i,j)=max \left\{ \begin{array}{} max \{ dp(i-1,k)+\sum_{k=j-T}^{j}a[i][k]\} \\ max \{ dp(i-1,l)+\sum_{k=j}^{j+T}a[i][k]\} \end{array} \right\} \]

初始化\(dp(i,j)=-inf\)\(dp(0,x)=0\)
其中\(k\)是从该房间向左和向右走不超过\(T\)个房间到达的房间号,按照这个方程直接枚举\(k\)(O(T)),再计算连续和(O(T)),写出程序,时间复杂度\(O(n*m*T*T)=O(10^{14})\)妥妥的超时

于是想到要进行优化,很容易想到的一个是对每一层先计算出前缀和和后缀和,将计算连续和的时间减为O(1),转移方程(以下以前缀和为例)变为

\[dp(i,j)=max \left\{ \begin{array}{} max\{dp(i-1,k)+sum(j)-sum(k-1)|j-T<=k<=j\} \\ max\{dp(i-1,k)+sum(k)-sum(j-1)|j<=k<=j+T\} \end{array} \right\} \]

这里的sum(i)即前缀和

但是时间\(O(n*m*T)=O(10^{10})\)依然超时,还要继续优化。这时候从方程入手好像没有什么可以优化的地方了,所以就考虑从怎么减少枚举\(k\)的时间入手
接下来以向左走为例(开始抄讲稿)

设:\(v1=max\{dp(i-1,k)+sum(j)-sum(k-1) | j-T<=k<=j \}\)
变形为:\(v1=max\{dp(i-1,k)-sum(k-1) | j-T<=k<=j \} + sum(j)\)
观察:\(max\{dp(i-1,k) -sum(k-1) | j-T<=k<=j \}\)实质就是在窗口\([j-T,j]\)中选择 \(dp(i-1,k)-sum(k-1)\)最大的元素。

以上变形是可以用滑动窗口优化的关键,在窗口中只需要维护\(dp(i-1,k)-sum(k-1)\)的最大值而没有当前房间\(j\)的值干扰。
经过优化,每次只要取出队首和\(sum(j)\)相减就可以得到\(dp(i,j)\),枚举时间降至O(1),由于滑动窗口算法维护队列时间复杂度也是O(1),算法速度达到理论下界O(n*m)(因为至少每个元素都要访问一次)

最后\(Ans=max\{dp(n,j)|1<=j<=m\}\)

代码实现时空间复杂度可优化,可以读入一行处理一行,则a数组可以只开一行的大小,由于方程中当前行的状态只受前一行影响,dp数组可以优化为2层滚动使用。
具体代码见下:

#include<iostream>
#include<cstdio>
#include<cstring>
#include<cmath>
#include<queue>
#include<vector>
#include<algorithm>
#define inf 0x3f3f3f3f
#define maxn 105
#define maxm 10005
#define _for(i,a,b) for(int i=(a);i<(b);++i)
#define _rof(i,a,b) for(int i=(a);i>(b);--i)
#define _rep(i,a,b) for(int i=(a);i<=(b);++i)
#define _per(i,a,b) for(int i=(a);i>=(b);--i)
using namespace std;

int n,m,x,T;
int a[maxm];//每读一层当场处理,只要一维
int dp[2][maxm],now;//滚动成2层 
int qz[maxm],hz[maxm];
struct room{
	int val;
	int id;
};
deque<room> q,h;//双端队列
int main(){
	scanf("%d%d%d%d",&n,&m,&x,&T);
	
	now=0;
	_rep(j,1,m)dp[now][j]=-inf;//初始化
	dp[now][x]=0;
	
	_rep(i,1,n){
		now^=1;
		
		_rep(j,1,m)scanf("%d",&a[j]);
		
		q.clear();
		_rep(j,1,m){
			qz[j]=qz[j-1]+a[j];
			
			int t=dp[now^1][j]-qz[j-1];
			while(!q.empty() && q.back().val < t)q.pop_back();
			q.push_back((room){t,j});
			
			while(!q.empty() && q.front().id+T<j)q.pop_front();
			dp[now][j]=q.front().val+qz[j];//因为是第一次更新,这里不需要max 
		}
		
		h.clear();
		_per(j,m,1){
			hz[j]=hz[j+1]+a[j];
			
			int t=dp[now^1][j]-hz[j+1];
			while(!h.empty() && h.back().val < t)h.pop_back();
			h.push_back((room){t,j});
			
			while(!h.empty() && h.front().id-T>j)h.pop_front();
			dp[now][j]=max(dp[now][j], h.front().val+hz[j]);//这里需要,以判断从左边过来好还是右边过来好 
		}
	}
	
	int ans=0;
	_rep(j,1,m)ans=max(ans,dp[now][j]);
	printf("%d",ans);
	return 0;
}
posted @ 2019-07-23 20:44  Deguassing-compass  阅读(253)  评论(0编辑  收藏  举报