烽火傳遞(dp+單調佇列)
阿新 • • 發佈:2018-12-17
烽火臺又稱烽燧,是重要的軍事防禦設施,一般建在險要或交通要道上。一旦有敵情發生,白天燃燒柴草,通過濃煙表達資訊;夜晚燃燒乾柴,以火光傳遞軍情,在某兩座城市之間有 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; }