烽火传递(dp+单调队列)

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

第一行:两个整数 N,M。其中N表示烽火台的个数, M 表示在连续 m 个烽火台中至少要有一个发出信号。接下来 N 行,每行一个数 Wi,表示第i个烽火台发出信号所需代价。

Output

一行,表示答案。

Sample Input

5 3  
1  
2  
5  
6  
2

Sample Output

4

Data Constraint

对于50%的数据,M≤N≤1,000 。 对于100%的数据,M≤N≤100,000,Wi≤100。
 

分析:

定义f[i]为点燃第i个烽火时前i个烽火满足条件的最小值。则状态转移方程为:

f[i]=min(f[j])+a[i].  其中 i-m<=j<=i-1;

由此可以得出一般的解法:

#include <iostream>//烽火传递问题。 
using namespace std;
const int inf=1e6+7;
int a[inf];
int f[inf];
/*状态转移方程 : 
定义f[i]: 点燃第i个烽火,并且前i个满足条件。
则:f[i]=(min)f[j]+a[i]   i-m<=j<=i-1;  状态转移方程。 
*/ 

int main()
{
	int n,m;
	scanf("%d %d",&n,&m);
	for(int i=1;i<=n;i++)
		scanf("%d",&a[i]);
	f[0]=0;
	for(int i=1;i<=m;i++)
		f[i]=a[i];
	for(int i=m+1;i<=n;i++)
	{
		long long themax= ~(1<<31);
		for(int j=i-m;j<=i-1;j++)
		{
			if(f[j]<themax)
				themax=f[j];			
		}
		f[i]=themax+a[i];
	}
	long long tmax=1<<30;
	for(int i=n;i>n-m;i--)
	{
		if(f[i]<tmax)
			tmax=f[i];
	}	 
	cout<<tmax<<endl;
	return 0;	
} 

但是从以上程序中也可以看出复杂度为O(n的平方),对应题目而言显然是不行的,并且注意到是线性结构,求其最小值,所以可以使用单调队列进行优化。单调队列我认为和单调函数差不多,不是单调递增就是单调递减,只要在入队的时候保持队列的单调性就行了,并且弹出比入队元素大的元素就可以了。

代码:
 

#include <iostream>	  //单调队列+dp 
using namespace std;
const int inf=1e6+7;
int a[inf];
int que[inf];  
int f[inf];
/*
转移方程:f[i]=min(f[j])+a[i];
是从f【i】入队的吧。  
*/ 
int main()			     
{
	int n, m;
	int head=1,tail=0;
	scanf("%d %d",&n,&m);
	for(int i=1;i<=n;i++)
		scanf("%d",&a[i]);
		
	for(int i=0;i<n;i++)			//其中的过程,其中的状态转移过程。 
	{
		while(tail>=head && f[i]<=f[que[tail]])tail--;   //d出队。
		que[++tail]=i;
		while(tail>=head && que[head]<i+1-m)head++;
		f[i+1]=f[ que[head] ]+a[i+1]; 
	}
	
	int ans=~(1<<31);
	for(int i=n;i>n-m;i--)		  
		ans=min(f[i],ans);
	cout<<ans<<endl;
	return 0;	
} 

猜你喜欢

转载自blog.csdn.net/weixin_40799464/article/details/83304057