单调队列——烽火传递

nkoj 2151

Description

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

Input

第一行有两个数n,m(1<=n,m<=1000000)分别表示n个烽火台,在m个烽火台中至少要有一个发出信号。 
第二行为n个数,表示每一个烽火台的代价。 

Output

一个数,即最小代价。

Sample Input

5 3
1 2 5 6 2

Sample Output

4
分析:
应该算是滑动窗口模型,再结合动态规划。
状态: f[i]表示从1到i满足题意,并选中第i个烽火台的最小代价。
那么 f[i]= f[i]+min(f[j])  (i-m<=j <= i -1)
min(f[j])就利用滑动窗口的思想计算了。
代码如下:
#include
#include
using namespace std;
const int maxn=1000000+5;
int n,m,s[maxn],f[maxn],pos[maxn],q[maxn];
//多关键字队列,q为烽火台的代价,pos为下标; 
inline void _read(int &x){
	char ch=getchar(); bool mark=false;
	for(;!isdigit(ch);ch=getchar())if(ch=='-')mark=true;
	for(x=0;isdigit(ch);ch=getchar())x=x*10+ch-'0';
	if(mark)x=-x;
}
int main(){
	int i,j,ans=2e9,front,rear;
	_read(n);_read(m);
	for(i=1;i<=n;i++)_read(s[i]);
	front=rear=1;
	rear++;   //一定要先push一个空元素(血的教训) ; 
	for(i=1;i<=n;i++){  //滑动窗口最小值,维护q递增,且首尾下标差不超过m; 
		while(front!=rear&&i-pos[front]>m)front++;  //  下标差大于m 
		f[i]=s[i]+q[front];     //此时,q[front]就是min(s[j])(i-m<=j<=i-1); 
		while(front!=rear&&f[i]n-m;i--)ans=min(ans,f[i]);  	//找答案 
	printf("%d",ans);
}

类似的题:nkoj 3661 修剪草坪:
输入大致相同,n个数中若不能连续选择k个数,那么选出数的最大和是多少?
如果关注题意的反面,就会发现这和烽火传递其实是一道题:
反面:每m+1只牛中,至少有一只不选,求不选的和的最小值。不就是烽火传递吗?
stl版

#include
#include
#include
#define inf 99999999999999LL
using namespace std;
long long a[1000005];
struct node{
	long long n;
	long long w;
	node(long long n,long long w):n(n),w(w){}
};
dequeq;
long long f[1000005];
int main(){
	long long n,m,i,j,k,ans;
	ans=inf;
	cin>>n>>m;
	for(i=1;i<=n;i++){
		scanf("%d",&a[i]);
	}
	for(i=0;i<=n;i++){
		f[i]=a[i]+q.front().w;
		while(q.size()&&q.back().w>f[i])q.pop_back();
		q.push_back(node(i,f[i]));
		while(q.size()&&q.front().nn-m;i--){
		ans=min(ans,f[i]);
	}
	cout<

你可能感兴趣的:(单调队列,动态规划)