1019 - 单调队列+dp - 烽火传递

版权声明:虽然我只是个小蒟蒻但转载也请注明出处哦 https://blog.csdn.net/weixin_42557561/article/details/83188100

题目描述

烽火台又称烽燧,是重要的防御设施,一般建在险要处或交通要道上。一旦有敌情发生,白天燃烧柴草,通过浓烟表达信息:夜晚燃烧干柴,以火光传递军情。在某两座城市之间有 n 个烽火台,每个烽火台发出信号都有一定的代价。为了使情报准确的传递,在 m 个烽火台中至少要有一个发出信号。现输入 n、m 和每个烽火台发出的信号的代价,请计算总共最少需要多少代价,才能使敌军来袭之时,情报能在这两座城市之间准确的传递!

输入格式

第一行有两个数 n,m 分别表示 n 个烽火台,在任意连续的 m 个烽火台中至少要有一个发出信号。
第二行为 n 个数,表示每一个烽火台的代价。

输出格式

一个整数,即最小代价。

样例数据 1

输入 

5 3 
1 2 5 6 2

输出

4

备注

【数据范围】
1<=n,m<=1,000,000,保证答案在 int 范围内。

分析

单调队列

定义: f[i]-->第 i 位发出信号,且前面都满足要求的最小代价 
转移:f[i]=min{f[j]}+a[i]  (i-m<=j<i) 

那么由于转移的时候只和前面 m 个位置有关系,且是取这个区间内的最小值

我们就不用O(n*m)来转移了

发现每次 i 调大最多只允许多一个 j 进入决策区间,最多只有一个 j 退出决策区间

我们可以用线段树来维护区间的最小值,这是O(n log m )的,但常数有点大啊……

我们也可以用单调队列(想象为一个窗口,每次只允许 m 个数出现)来维护当前决策区间的最小值

这样就是O(1)寻找最小值的了

(注意单调队列中保证下标递增,对应的值也递增(或递减))

还有一些小细节要注意,写在代码里了

代码

#include<bits/stdc++.h>
#define in read()
#define N 1000009
using namespace std;
inline int read(){
	char ch;int f=1,res=0;
	while((ch=getchar())<'0'||ch>'9') if(ch=='-') f=-1;
	while(ch>='0'&&ch<='9'){
		res=(res<<3)+(res<<1)+ch-'0';
		ch=getchar();
	}
	return f==1?res:-res;
}
int n,m,a[N],q[N],head=1,tail=1;//注意tail一定要赋为1,因为前m个位置只用算自己 
int f[N];
//f[i]-->第 i 位发出信号,且前面都满足要求的最小代价 
//f[i]=min{f[j]}+a[i]  (i-m<=j<i) 
int main(){
	n=in;m=in;
	int i,j;
	for(i=1;i<=n;++i){
		a[i]=in;
		while(head<=tail&&i-q[head]>m) ++head;
		f[i]=f[q[head]]+a[i];
		while(head<=tail&&f[i]<=f[q[tail]]) tail--;
		q[++tail]=i;
	}
	int res=(1ll<<31)-1;
	for(i=n-m+1;i<=n;++i) res=min(res,f[i]);
	cout<<res;
	return 0;
}

猜你喜欢

转载自blog.csdn.net/weixin_42557561/article/details/83188100
今日推荐