单调队列 单调栈学习总结

之前从没有掌握的一些线性数据结构今天终于学到了。

吐槽一句:直接放单调队列优化DP是何意味,感觉没有放什么单调队列经典例题。

单调队列优化DP

当你发现一个题目可以从一个固定长度的窗口转移过来时,可以用单调队列维护这个窗口的最值,更好的去DP

如果长度不固定的话只能用线段树优化 DP 了。

A

单调队列模板题。

std::cin >> n >> m;
rep(i, 1, n) std::cin >> a[i];
rep(i, 1, n) {
    while (!q1.empty() && a[q1.back()] <= a[i]) q1.pop_back();
    while (!q2.empty() && a[q2.back()] >= a[i]) q2.pop_back();
    q1.pb(i), q2.pb(i);
    if (q1.front() < i - m + 1) q1.pop_front();//如果当前元素已经在窗口外面了,就将其弹出
    if (q2.front() < i - m + 1) q2.pop_front();
    if (i >= m) ans1.pb(a[q2.front()]), ans2.pb(a[q1.front()]);//每个窗口的最值
}
for (auto &v : ans1) std::cout << v << ' ';
std::cout << '\n';
for (auto &v : ans2) std::cout << v << ' ';
std::cout << '\n';

B

单调队列优化 DP 模板题。

\(dp_i\) 表示跳到位置 \(i\) 时的最大收益。

注意到 \(dp_{i + l}\) 可从窗口 \(dp_{[i + l - r\ ,\ i ]}\) 转移过来,窗口长度始终为 \(r - l\)

于是我们用单调队列来维护。

当位置 \(i + l > n\) 时就说明到了河对岸,可以更新答案。

std::cin >> n >> l >> r;
rep(i, 0, n) std::cin >> a[i];
std::fill(dp, dp + n + 1, -inf);
dp[0] = 0;
q.pb(0);
rep(i, 0, n) {
    dp[i] += a[i];
    while (!q.empty() && i - q.front() > r - l) q.pop_front();
    while (!q.empty() && dp[q.back()] < dp[i]) q.pop_back();
    q.pb(i);
    dp[i + l] = dp[q.front()];
    if (i + l > n) ans = std::max(ans, dp[i + l]);
    // dp[i + r] = dp[q.front()];
    // if (i + r > n) ans = std::max(ans, dp[i + r]);
}
std::cout << ans << '\n';

不知道为什么把注释去掉答案也是对的,但是不符合逻辑。

C

先做一遍滑动窗口,找到最大和最小的窗口值,存下来。

然后再在这个最大和最小的上面找的最大和最小的,做差更新答案。

std::cin >> n >> m >> k;
rep(i, 1, n) rep(j, 1, m) std::cin >> a[i][j];
rep(i, 1, n) {
    miq.clear(), mxq.clear();
    rep(j, 1, m) {
        while (!mxq.empty() && j - mxq.front() >= k) mxq.pop_front();
        while (!mxq.empty() && a[i][mxq.back()] <= a[i][j]) mxq.pop_back();
        mxq.pb(j);
        while (!miq.empty() && j - miq.front() >= k) miq.pop_front();
        while (!miq.empty() && a[i][miq.back()] >= a[i][j]) miq.pop_back();
        miq.pb(j);
        if (j >= k) mx[j - k + 1][i] = a[i][mxq.front()], mi[j - k + 1][i] = a[i][miq.front()];
    }
}
m = m - k + 1;
std::swap(n, m);
rep(i, 1, n) {
    miq.clear(), mxq.clear();
    rep(j, 1, m) {
        while (!mxq.empty() && j - mxq.front() >= k) mxq.pop_front();
        while (!mxq.empty() && mx[i][mxq.back()] <= mx[i][j]) mxq.pop_back();
        mxq.pb(j);
        while (!miq.empty() && j - miq.front() >= k) miq.pop_front();
        while (!miq.empty() && mi[i][miq.back()] >= mi[i][j]) miq.pop_back();
        miq.pb(j);
        if (j >= k) ans = std::min(ans, mx[i][mxq.front()] - mi[i][miq.front()]);
    }
}
std::cout << ans << '\n';

D

\(dp_i\) 表示跳到位置 \(i\) 的最小次数。

注意到 \(dp_{i + 1}\) 可以从 \(dp_{[i - m + 1, i]}\) 转移过来,窗口长度为 \(m\) ,只需用单调队列维护最值即可。

我们注意到为游戏结束格子的一定不能跳到, 所以 \(dp\) 值设为 \(\inf\)

最后方案输出倒着输就行了。

std::cin >>n >> m;
std::cin >> s;
std::fill(dp + 1, dp + n + 1, inf);
dp[0] = 0;
rep(i, 0, n) {
    if (s[i] == '1') dp[i] = inf;
    while (!q.empty() && i - q.front() + 1 > m) q.pop_front();
    while (!q.empty() && dp[i] < dp[q.back()]) q.pop_back();
    q.pb(i);
    if (dp[q.front()] ^ inf) dp[i + 1] = dp[q.front()] + 1, lst[i + 1] = q.front();
}
if (dp[n] > n) return std::cout << "-1\n", 0;
std::stack<int> st;
int pos = n;
while (pos) st.push(pos - lst[pos]), pos = lst[pos];
while (!st.empty()) std::cout << st.top() << ' ', st.pop();

F

就是用单调栈找到每个数子左边比它大的,再找的右边比它大的,然后在做一个异或就完了。

std::cin >> n;
rep(i, 1, n) std::cin >> a[i];
rep(i, 1, n) {
    while (!st.empty() && a[st.top()] < a[i]) st.pop();
    if (!st.empty()) ans = std::max(ans, a[st.top()] ^ a[i]);
    st.push(i);
}
frep(i, n, 1) {
    while (!st.empty() && a[st.top()] < a[i]) st.pop();
    if (!st.empty()) ans = std::max(ans, a[st.top()] ^ a[i]);
    st.push(i);
}
std::cout << ans << '\n';

被单调队列了怎么办?

图片中的人是 jiangly 。

posted @ 2026-04-21 10:46  yixinc  阅读(21)  评论(0)    收藏  举报