单调队列——烽火传递

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<cstdio>
#include<iostream>
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]<q[rear-1])rear--;
		q[rear]=f[i];pos[rear]=i;rear++;  //相当于push 
	}
	for(i=n;i>n-m;i--)ans=min(ans,f[i]);  	//找答案 
	printf("%d",ans);
}

类似的题:nkoj 3661 修剪草坪:
输入大致相同,n个数中若不能连续选择k个数,那么选出数的最大和是多少?
如果关注题意的反面,就会发现这和烽火传递其实是一道题:
反面:每m+1只牛中,至少有一只不选,求不选的和的最小值。不就是烽火传递吗?
stl版
#include<iostream>
#include<cstdio>
#include<deque>
#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){}
};
deque<node>q;
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().n<i-m+1)q.pop_front();
	}
	for(i=n;i>n-m;i--){
		ans=min(ans,f[i]);
	}
	cout<<ans;
}

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值