【NOI2005】瑰丽华尔兹(单调队列优化dp)

首先设计状态方程。二维的(x,y)是不可避免了,但又不足以描述状态,所以设一个f[k][x][y]表示在前k个时间段内,终点为(x,y)格子的最长滑行距离。

每个时间段,每个格子有两大类:

一,最简单的,不动,f[k][x][y]=max(f[k][x][y],f[k-1][x][y]) 。

二,要动,其实不麻烦,只是需要分开讨论,顺便注意边界和障碍物:

1,d=1,f[k][x][y]=max(f[k-1][x+n][y]+n),0<=n<=t_i-s_i+1 。 竖着向上滑。

2,d=2,f[k][x][y]=max(f[k-1][x-n][y]+n),0<=n<=t_i-s_i+1  。竖着向下滑。

3,d=3, f[k][x][y]=max(f[k-1][x][y+n]+n),0<=n<=t_i-s_i+1 。横着向左滑。

4,d=4,f[k][x][y]=max(f[k-1][x][y-n]+n),0<=n<=t_i-s_i+1  。横着向右滑。

(其实这个式子还是很好得到的)

接着我们想一想一般思路如何实现。

首先一层循环K,省掉s,t,d数组,输入一组就做一次dp。

然后根据d的方向,x或者y一有个是确定的,假定x不变。一层循环这个确定的x。

接着是一层枚举y。——我们发现到这里已经是K*N*M了,极端情况是8000000。

然而根据上面的状态转移方程,还需要枚举一层f[K-1][x][y']里的y',(y在变),又要乘上一个M<=200,超时。

有这么几个优化:

1,空间优化,使用滚动数组省掉K这一维,那么第一种不动的情况已自动算上。

2,代码优化,写一个solve(x,y,len,dir)函数,表示从(x,y)开始做dp,ti-si+1等于len,方向为dir。这样main函数里只需写一点点就够了。

3,重点,时间优化,用单调队列。以要动的第1种情况为例。由于我们已经使用滚动数组省掉了K,并且假定y是不变的,下面为了方便起见,只写出x这一维。

对于f[x]=max(f[x+n]+n),0<=n<=t_i-s_i+1,假如x<=4,len=1。

f[4]可以用到f[4]。

f[3]可以用到f[4],f[3]。

f[2]可以用的到f[3],f[2]。 

f[1]可以用到f[2],f[1]。

是不是想起了滑动窗口?就是这个。第一种情况为例,由于我们省掉了K这一维,f[x]未更新时就相当于[K-1]这一层,更新后就是[K]这一层了。而我们更新需要用到[K-1]层,所以我们做逆推,在更新前就把它入队作为[K-1]层的值,另用一个计数来记录两个状态之间差了多少格。具体实现看代码。

#include<bits/stdc++.h>
using namespace std;
const int MAXN=201;
int N,M,X,Y,K,mp[MAXN][MAXN],f[MAXN][MAXN];
int dx[5]={0,-1,1,0,0},dy[5]={0,0,0,-1,1};
struct data{int num,dis;}q[MAXN];

char c;
void scan(int &x)
{
	for(c=getchar();c<'0'||c>'9';c=getchar());
	for(x=0;c>='0'&&c<='9';c=getchar()) x=x*10+c-'0';
}

void solve(int x,int y,int len,int dir)
{
	int L=1,R=0,i;
	for(i=1;x>=1&&x<=N&&y>=1&&y<=M;i++,x+=dx[dir],y+=dy[dir]) //边界 
	{
		if(mp[x][y])
		{
			L=1;R=0;
			continue;
		}
		while(L<=R && i-q[L].dis>len) L++; //超过步数限制 
		while(L<=R && f[x][y]>=q[R].num-q[R].dis+i) R--; //不够优 
		q[++R]=((data){f[x][y],i});  //更新前的数据入队,记录从起点走了多远 
		f[x][y]=q[L].num-q[L].dis+i;
	}
}

int main()
{
	int i,j,S,T,D;
	char c;
	memset(f,0xf3,sizeof(f));  //负无穷 
	scan(N);scan(M);scan(X);scan(Y);scan(K);
	for(i=1;i<=N;i++)
	for(j=1;j<=M;j++)
	{
		cin>>c;
		if(c=='x')
			mp[i][j]=1; 
	}
	f[X][Y]=0;
	
	while(K--)
	{
		scan(S);scan(T);scan(D);
		
		if(D==1) 
			for(j=1;j<=M;j++)
				solve(N,j,T-S+1,D);
				
		else if(D==2)
			for(j=1;j<=M;j++)
				solve(1,j,T-S+1,D);
				
		else if(D==3)
			for(i=1;i<=N;i++)
				solve(i,M,T-S+1,D);
				
		else
			for(i=1;i<=N;i++)
				solve(i,1,T-S+1,D);
	}
	
	int ans=0;
	for(i=1;i<=N;i++)
		for(j=1;j<=M;j++)
			ans=max(ans,f[i][j]);
	cout<<ans;
	return 0;
}

 

猜你喜欢

转载自blog.csdn.net/WWWengine/article/details/82221017