[NOI2005] 瑰丽华尔兹

本文解析了NOI2005瑰丽华尔兹题目,介绍了一个N×M矩阵中,T个时间段内,角色可以在不碰撞边界和障碍的情况下向某一方向滑动的问题。通过动态规划算法,利用单调队列维护状态,求解最长滑动距离。

[NOI2005] 瑰丽华尔兹

\[ \mathfrak{<<The Crave>>} \]

题目大意:\(N\times M\)的矩阵,\(T\)个时间段,某个特定的时间段可以向一个方向滑动或不滑动,不可以撞到边界和障碍,求最长滑动距离

Solution

  • \({f[k][i][j]}\)为第\(i\)个时间段结束时走到\((i,j)\)的最长滑动距离,无法到达表示为\(-\infty\),设\(len=t_k-s_k+1\),即第\(k\)个时间段的持续时间
  • \(f[k][i][j]=\max\limits_{j-len\leq t\leq j}(f[k-1][i][t]+j-t)=\max\limits_{j-len\leq t\leq 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;
}

转载于:https://www.cnblogs.com/LMSH7/p/9684632.html

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值