1. 程式人生 > >烽火傳遞(dp+單調佇列)

烽火傳遞(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;	
}