1、
烽火传递
朴素的dp过程不难理解,dp[i]表示:考虑前i个烽火台 且第i个点燃状态下的最小代价。
dp[i]=min(dp[j])+a[i] ( i-m<=j<=i-1)
j的范围:在考虑i之前,i前面的m个元素,必须取1个,比如i=7,m=3 ,考虑i=7之前,4 5 6中必须取1个。
用单调队列可以优化这种类问题。
所谓单调队列 ,就是 不断地向缓存数组里读入元素,也不时地去掉最老的元素,不定期的询问当前缓存数组里的最小的元素。
保证每次查询区间最值时可以用O(1)时间查到。
维护单调队列时,对于新元素,我们先按照单调性从队尾删去部分元素,再让新元素入队,保证单调性不变 。然后由于更新队首,把超出区间长度的元素丢出去。
注:单调队列存的是数组下标,不是真的存值。而且队头到队尾的元素,也就是下标是递增的,表示入队的时间先后。
//#pragma comment(linker, "/STACK:102400000,102400000")//手动扩栈
#include<iostream>
#include<algorithm>
#include<cstdio>
#include<stdlib.h>
#include<string.h>
#include<string>
#include<vector>
#include<stack>
#include<queue>
#include<map>
#include<math.h>
#include<set>
#include<deque>
#include<unordered_map>
using namespace std;
#define LL long long
#define ULL unsigned long long
const int INF=0x3f3f3f3f;
const double eps=1e-5;
const int maxn=2e5+7;
deque<int> dq;//单调队列 递增
LL dp[maxn];//前i个烽火台 且第i个点燃的最小代价
int n,m,v[maxn];
int main()
{
// ios::sync_with_stdio(false);
// cin.tie(0);
// cout.tie(0);
scanf("%d%d",&n,&m);
for(int i=1;i<=n;i++)
{
scanf("%d",v+i);
}
dq.push_front(0);//这题我们初始化队列为0,可以保证前m个当中不会取超过2个元素
for(int i=1;i<=n;i++)
{
dp[i]=v[i];
if(dq.size()) dp[i]+=dp[dq.front()];
//入队
while(dq.size() && dp[dq.back()]>dp[i]) dq.pop_back();
dq.push_back(i);
//队首出队
while(dq.size() && dq.front()<i-m+1) dq.pop_front();
}
LL ans=dp[n];
for(int i=n-1;i>n-m;i--) ans=min(ans,dp[i]);
cout<<ans;
//system("pause");
return 0;