[NOI2005] 瑰丽华尔兹
[mathfrak{<<The Crave>>}
]
题目大意:(N imes M)的矩阵,(T)个时间段,某个特定的时间段可以向一个方向滑动或不滑动,不可以撞到边界和障碍,求最长滑动距离
Solution
- ({f[k][i][j]})为第(i)个时间段结束时走到((i,j))的最长滑动距离,无法到达表示为(-infty),设(len=t_k-s_k+1),即第(k)个时间段的持续时间
- (f[k][i][j]=maxlimits_{j-lenleq tleq j}(f[k-1][i][t]+j-t)=maxlimits_{j-lenleq tleq j}(f[k-1][i][t]-t)+j)(这是向右走)
我们可以维护一个单调队列,保存(k−1)时刻的所有可行状态的(f)值与(t),保证(t)单调递增,(f)值单调递减,在(dp)到第(t)段时,首先枚举行(i),然后从左到右来枚举(j),并时刻保证队首的(t)满足限制条件j−t≤len,这样的单调队列的队首显然是最优的,用此时的队首的(f[k−1][i][t]+j-t)来更新答案。
Code
#include <bits/stdc++.h>
using namespace std;
const int N = 205;
const int INF = 2147483644;
const int dx[] = {0, -1, 1, 0, 0};
const int dy[] = {0, 0, 0, -1, 1};//第一位是0
char qwq[N][N];
int n, m, T, ans;
int dp[N][N];
struct Node {
int dp, pos;
}q[N];//一行或一列
void solve(int x, int y, int len, int d) {
int head = 1, tail = 0;
for(int i = 1; x >= 1 && x <= n && y >= 1 && y <= m; i++, x += dx[d], y += dy[d]) {
//一直朝一个方向走
if(qwq[x][y] == 'x') head = 1, tail = 0;//之前的都清除
else {
while(head <= tail && q[tail].dp + i - q[tail].pos < dp[x][y]) tail--;//不符合单调递减的单调队列性质
q[++tail] = Node{dp[x][y], i};//入队
if(q[tail].pos - q[head].pos > len) head++;//队首元素超出"max()"的范围
dp[x][y] = q[head].dp + i - q[head].pos;//i-q[head].pos 就是这个时间段走的距离
ans = max(ans, dp[x][y]);
}
}
return;
}
int main () {
int sx, sy;
scanf("%d %d %d %d %d", &n, &m, &sx, &sy, &T);
for(int i = 1; i <= n; ++i)
scanf("%s", qwq[i] + 1);
for(int i = 1; i <= n; ++i)
for(int j = 1; j <= m; ++j) //
dp[i][j] = -INF;
dp[sx][sy] = 0;//其他地方都是-INF
for(int i = 1, s, t, d, len; i <= T; ++i) {
scanf("%d %d %d", &s, &t, &d);
len = t - s + 1;
if(d == 1) for(int j = 1; j <= m; ++j) solve(n, j, len, d);//向上
if(d == 2) for(int j = 1; j <= m; ++j) solve(1, j, len, d);//向下
if(d == 3) for(int j = 1; j <= n; ++j) solve(j, m, len, d);//向左
if(d == 4) for(int j = 1; j <= n; ++j) solve(j, 1, len, d);//向右
}
cout << ans << endl;
return 0;
}