P3572 [POI2014] PTA-Little Bird

原题链接

题解

首先,考虑接下来往哪颗树飞是很困难的,因为当前的决策会影响之后的决策

但是如果考虑到达当前树从哪里飞过来就比较好了,因为无后效性

接着我们可以暴力做法,遍历每棵树从前 \(k\) 个树飞过来的值,然后取最小的那个,但是这样显然会超时,所以我们优化一下

有哪些值得被优化的地方?--有很多无用决策点

什么是无用决策点?请看下文

对于两棵相邻的树,\(i,i+1\),设 \(dp[i]\) 为到达该树的最小疲劳值

  • 如果 \(dp[i]\geq dp[i+1]\) 同时 \(h[i]\leq h[i+1]\)

那么我们可以抛弃转移点 \(i\),因为能从 \(i\) 飞过来的选择,也一定能从 \(i+1\) 飞过来,且不劣

  • 如果 \(dp[i] > dp[i+1]\) 同时 \(h[i]\gt h[i+1]\)

那么我们可以抛弃转移点 \(i\),因为能从 \(i\) 飞过来的选择,也一定能从 \(i+1\) 飞过来,且不劣

解释:如果后面有 \(h[i+1] \lt h[j] \leq h[i]\) ,那么两个转移点的效果是一样的,否则 \(i+1\) 更优

  • 如果 \(dp[i] == dp[i+1]\) 同时 \(h[i]\gt h[i+1]\)

都保留,因为对于 \([i+2,i+k]\) 的树来说,从 \(i\) 转移过来更优,但是 \(i+k+1\) 或许需要转移点 \(i+1\)

  • 如果 \(dp[i] < dp[i+1]\)

都保留,因为对于 \([i+2,i+k]\) 的树来说,从 \(i\) 转移过来更优,但是 \(i+k+1\) 或许需要转移点 \(i+1\)

因此,我们可以维护一个队列,该队列特性为从队首到队尾,其下标递增,疲劳值递增,疲劳值相同的点树的高度递减

code

#include<bits/stdc++.h>
#define ll long long
using namespace std;

int dp[1000006]={0},h[1000006]={0};

void solve()
{
    int n;
    cin>>n;

    for(int i=1;i<=n;i++) cin>>h[i];

    int q;
    cin>>q;

    while(q--)
    {
        int k;
        cin>>k;

        deque<int> pre;

        pre.push_back(1);
        dp[1]=0;

        for(int i=2;i<=n;i++)
        {
            if(pre.front()<i-k) pre.pop_front();

            dp[i]=dp[pre.front()]+(h[i]>=h[pre.front()]);

            while(pre.size()&&(dp[pre.back()]>dp[i]||dp[pre.back()]==dp[i]&&h[pre.back()]<=h[i])) pre.pop_back();

            pre.push_back(i);

            //cout<<dp[i]<<'\n';
        }

        cout<<dp[n]<<'\n';
        //cout<<'\n';
    }
}
int main()
{
    ios::sync_with_stdio(false);cin.tie(0);cout.tie(0);
    int t=1;
    //cin>>t;
    while(t--) solve();
    return 0;
}


posted @ 2024-07-22 18:13  纯粹的  阅读(19)  评论(0)    收藏  举报