Sugoroku(dp,单调队列)

题意

有一行格子(\(0 \sim N\)),你现在位于\(0\)号格子。

你每次可以走最多\(M\)步。有些格子不能停留(可以路过,但是不能是每次走的终点)

问到\(N\)号格子,最少需要走多少步,并输出字典序最小的路线。

题目链接:https://atcoder.jp/contests/abc146/tasks/abc146_f

数据范围

\(1 \leq N, M \leq 10^5\)

思路

考虑DP,设\(f(i)\)表示走到\(i\)号格子的最少步数。那么转移方程为:\(f(i) = \min\limits_{i - m \leq j < i} f(j) + 1\)。但是这样的时间复杂度是\(O(NM)\)的,因此需要考虑如何优化。

我们发现每次是找以\(i\)为右端点的\(m\)长度的区间最小值,这是一个经典的滑动窗口问题。因此可以使用单调队列进行优化。

代码

#include <iostream>
#include <cstdio>
#include <cstring>
#include <algorithm>
#include <queue>
#include <vector>

using namespace std;

const int N = 100010;

int n, m;
char s[N];
int f[N], bk[N];
deque<int> que;
vector<int> ans;

int main()
{
    scanf("%d%d", &n, &m);
    scanf("%s", s);
    if(s[0] == '1' || s[n] == '1') {
        printf("-1\n");
        return 0;
    }
    memset(bk, -1, sizeof bk);
    bool flag = true;
    que.push_back(0);
    for(int i = 1; i <= n; i ++) {
        if(que.size() && i - que.front() > m) que.pop_front();
        if(s[i] == '0') {
            if(!que.size()) {
                flag = false;
                break;
            }
            f[i] = f[que.front()] + 1, bk[i] = que.front();
            while(que.size() && f[que.back()] > f[i]) que.pop_back();
            que.push_back(i);
        }
    }
    if(!flag) printf("-1\n");
    else {
        int last = n;
        for(int i = bk[n]; ~i; i = bk[i]) {
            ans.push_back(last - i);
            last = i;
        }
        reverse(ans.begin(), ans.end());
        for(auto p : ans) printf("%d ", p);
        printf("\n");
    }
    return 0;
}
posted @ 2022-07-15 10:44  pbc的成长之路  阅读(39)  评论(0)    收藏  举报