原题链接
题意简化:
一个矩阵里,'.' 表示可以走,'x' 表示不能走
给出 k 个时间段,每段内,规定一个方向,每个单位时间内,只能朝当前格子 那个方向 前进1格 或者 选择不动
求最大行走长度?
设 i* , j* 为上一个合理的位置
首先考虑对于时间t来dp:
f[t][i][j] 表示在第 t 时刻在第 i 行第 j 列所能获得的最长距离
转移方程: f[t][i][j]=max(f[t-1][i][j],f[t][i*][j*]+1)
这样时间复杂度为 O(TNM),可以过 50% ,但对于 100% TLE 且 MLE
需要优化
把时间t换成区间k:
f[k][i][j] 表示在第 k 段滑行区间中在位置 i , j 所能获得最长距离
转移方程: f[k][i][j]=max(f[k-1][i][j],f[k][i*][j*]+dis(i,j,i*,j*))
这个做法的时间复杂度是 O(kn^3),会 TLE
需要进一步优化
用单调队列优化掉内层的一个 n,就可以做到 O(kn^2),可以 AC
本代码中还使用了滚动数组优化
用单调递减队列求最大值时,遇到障碍清空整个队列即可
另外队列比较时需要加上偏移量 dis
#include<bits/stdc++.h>
using namespace std;
#define MAXN 205
struct node {
int dp, pos;//dp 当前距离总和 pos 在第k段时的下标
} q[MAXN]; //q为单调递减队列,要存位置信息用来计算共走了几步
int n, m, sx, sy, K, ans, dp[MAXN][MAXN];//dp 最长距离
const int dx[5] = {0, -1, 1, 0, 0};//x方向
const int dy[5] = {0, 0, 0, -1, 1};//y方向
char map[MAXN][MAXN];//地图
void work(int x, int y, int len, int d) { //第k个区间的时长为len,方向为d,起点坐标x,y
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 (map[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++; //队列长度超过len时队首弹出
dp[x][y] = q[head].dp + i - q[head].pos; //最优解是队首元素+移动距离(原来的距离+现在的距离)
//i-q[head].pos为它们间的距离
ans = max(ans, dp[x][y]); //记录结果,取最大值
}
}
int main() {
scanf("%d%d%d%d%d", &n, &m, &sx, &sy, &K);
for (int i = 1; i <= n; i++) scanf("%s", map[i] + 1);
memset(dp, 0x3f, sizeof(dp));
dp[sx][sy] = 0; //初始化,只有初始位置是0,其他都是负无穷
for (int k = 1, s, t, d, len; k <= K; k++) {
scanf("%d%d%d", &s, &t, &d);
len = t - s + 1;//时间
//四个方向
if (d == 1) for (int i = 1; i <= m; i++) work(n, i, len, d);
if (d == 2) for (int i = 1; i <= m; i++) work(1, i, len, d);
if (d == 3) for (int i = 1; i <= n; i++) work(i, m, len, d);
if (d == 4) for (int i = 1; i <= n; i++) work(i, 1, len, d);
}
printf("%d", ans);
return 0;
}