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;
}

浙公网安备 33010602011771号