P2254 瑰丽华尔兹

原题链接

题意简化:


一个矩阵里,'.' 表示可以走,'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% TLEMLE
  需要优化 
  

把时间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;
}

  • 4
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值