P3572 [POI2014]PTA-Little Bird

题目传送门

题意

从左到右有 \(n\) 颗树, 每棵树有高度, 从一颗树跳到一颗比自己矮的树不用花费,否则花费 \(1\)
在第 \(i\) 颗树时只能跳到 \([i+1, i+k+1]\),问从第一颗树到最后一颗的最小花费, \(O(n)\)

我其实不大喜欢这题,因为这题的关键是花费为 \(1\),对其他花费不适用。

不过这题单调队列维护两个单调性这个想法是挺好的!

先说正解把?实际上就是一个单调队列优化dp, 你从后往前dp,我们设\(f_i\) 表示从第\(i\) 个点到 \(n\) 号点的最小花费, 转移的时候我们直接从范围内答案最小的点中高度最小的进行转移。为什么是对的?因为考虑答案次大的至少比最小的大1, 我们想要从次大转移的原因是这个点可能很矮,不用花费, 但是因为花费为\(1\), 即便答案最小的高度很高,加上\(1\) 之后才刚好等于次大的, 所以直接从答案最小的转移即可。

比较妙的是, 我们要考虑两个单调性:高度和花费,看似不可以用单调队列维护, 其实是可以的。
因为实际上两个单调性是没有交集的, 花费不同直接按花费, 只有花费相同时才考虑高度。


既然都说了这题不够妙, 那就顺便给出我推的一些性质把!

对于一个点, 一种最优方案时直接跳到可以跳到的点中, 小于它的最远的点。

今天的题解就摆到这了, 读自证。

实现

#include <iostream>
#include <cstdio>
#include <queue>
using namespace std;

int read(){
    int num=0; char c=getchar();
    while(!isdigit(c)) c=getchar();
    while(isdigit(c)) num=num*10+c-'0', c=getchar();
    return num;
}

const int N = 1e6+100;
int n, T, d[N], f[N];

int main(){
    n = read();
    for(int i=1; i<=n; i++) d[i]=read();
    T = read();
    while(T--){
        int k = read();
        deque<int> q; q.push_back(n);
        for(int i=n-1; i; i--){
            while(q.front() > i+k) q.pop_front();
            f[i] = f[q.front()] + (d[q.front()] >= d[i]);
            while(!q.empty() && (f[q.back()] > f[i] || (f[q.back()]==f[i] && d[q.back()]>=d[i])) ) 
                q.pop_back();
            q.push_back(i);
        }
        printf("%d\n", f[1]);
    }
    return 0;
}
posted @ 2022-03-28 21:52  ltdJcoder  阅读(30)  评论(0编辑  收藏  举报