P2254 [NOI2005] 瑰丽华尔兹

时间:2021-07-18
本文章向大家介绍P2254 [NOI2005] 瑰丽华尔兹,主要包括P2254 [NOI2005] 瑰丽华尔兹使用实例、应用技巧、基本知识点总结和需要注意事项,具有一定的参考价值,需要的朋友可以参考一下。

瑰丽华尔兹

给定一张网格图和起点,其中有一些是障碍点,还给出一些时间段内的移动方向,

每个单位时间都可以选择 保持不动 或 向该时间段内的方向移动一格,求移动距离的最大值。

《海上钢琴师》是很美丽的电影,这道题同样很优美。

考虑比较暴力的 DP 方法,设 \(f(i,j,t)\) 表示在 \(t\) 时刻处于 \((i,j)\) 的最长距离,显然根据给出的方向比较好推转移方程。

但是不难发现,这个方法的时空都是 \(O(Tnm)\) 的,极限数据下不太行。

换一种状态表示的方式,设 \(f(i,j,k)\) 表示在第 \(k\) 段时间内到达 \((i,j)\) 的最长距离。

为了方便表述,考虑将同一方向的位置放到序列上,将 \((i,j)\) 压缩为 \(i\),则有:

\[f(i,k)=\max\limits_{i-j\leq L(k)}\{f(j,k-1)+i-j\} \]

其中 \(L(k)\) 表示第 \(k\) 段的持续时间。

这个看上去很厉害,但是实际上时间是 \(O(n^3k)\),同样不太行,但是可以简单剖析一下转移方程,转化为:

\[f(i,k)=\max\limits_{i-j\leq L(k)}\{f(j,k-1)-j\}+i \]

式子内的项只与 \(j\) 有关,这就是标准的单调队列优化的套路了。

然后这道题就用 \(O(n^2k)\) 的优美写法解决了,而且注意一下状态的转移顺序,还可以将空间缩小至 \(O(n^2)\)

#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;

const int N = 210;
int n, m, sx, sy, k, ans;
int f[N][N];
char a[N][N];
int dx[] = {0, -1, 1, 0, 0};
int dy[] = {0, 0, 0, -1, 1};
struct Queue{int pos, val;} q[N];

int read(){
	int x = 0, f = 1; char c = getchar();
	while(c < '0' || c > '9') f = (c == '-') ? -1 : 1, c = getchar();
	while(c >= '0' && c <= '9') x = x * 10 + c - 48, c = getchar();
	return x * f;
}

void Work(int x, int y, int L, int d){
	int l = 1, r = 0;
	for(int p = 1; x >= 1 && x <= n && y >= 1 && y <= m; p ++, x += dx[d], y += dy[d])
		if(a[x][y] == 'x') {l = 1, r = 0; continue;}
		else{
			while(l <= r && q[r].val - q[r].pos < f[x][y] - p) r --;
			q[++ r] = (Queue){p, f[x][y]};
			while(l <= r && p - q[l].pos > L) l ++;
			f[x][y] = q[l].val + p - q[l].pos;
			ans = max(ans, f[x][y]);
		}
}

int main(){
	n = read(), m = read(), sx = read(), sy = read(), k = read();
	memset(f, 0xcf, sizeof(f));
	f[sx][sy] = 0;
	for(int i = 1; i <= n; i ++) scanf("%s", a[i] + 1);
	for(int i = 1; i <= k; i ++){
		int s = read(), t = read(), d = read();
		if(d == 1) for(int j = 1; j <= m; j ++) Work(n, j, t - s + 1, d);
		if(d == 2) for(int j = 1; j <= m; j ++) Work(1, j, t - s + 1, d);
		if(d == 3) for(int j = 1; j <= n; j ++) Work(j, m, t - s + 1, d);
		if(d == 4) for(int j = 1; j <= n; j ++) Work(j, 1, t - s + 1, d);
	}
	printf("%d\n", ans);
	return 0;
}

原文地址:https://www.cnblogs.com/lpf-666/p/15027816.html