题解
首先,考虑接下来往哪颗树飞是很困难的,因为当前的决策会影响之后的决策
但是如果考虑到达当前树从哪里飞过来就比较好了,因为无后效性
接着我们可以暴力做法,遍历每棵树从前 \(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;
}
标签:pre,Little,int,back,POI2014,转移,飞过来,Bird,dp
From: https://www.cnblogs.com/pure4knowledge/p/18316638