1. 程式人生 > 實用技巧 >Mowing the Lawn G「單調佇列優化DP」

Mowing the Lawn G「單調佇列優化DP」

Mowing the Lawn G「單調佇列優化DP」

題目描述

在一年前贏得了小鎮的最佳草坪比賽後,Farm John變得很懶,再也沒有修剪過草坪。現在,新一輪的最佳草坪比賽又開始了,Farm John希望能夠再次奪冠。

然而,Farm John的草坪非常髒亂,因此,Farm John只能夠讓他的奶牛來完成這項工作。Farm John有\(N\)(\(1 <= N <= 100,000\))只排成一排的奶牛,編號為\(1...N\)。每隻奶牛的效率是不同的,奶牛i的效率為\(E_i\)(\(0 <= E_i <= 1,000,000,000\))。

靠近的奶牛們很熟悉,因此,如果Farm John安排超過\(K\)

只連續的奶牛,那麼,這些奶牛就會罷工去開派對:)。因此,現在Farm John需要你的幫助,計算FJ可以得到的最大效率,並且該方案中沒有連續的超過\(K\)只奶牛。

輸入格式

第一行:空格隔開的兩個整數 \(N\)\(K\)

第二到 \(N+1\) 行:第 \(i+1\) 行有一個整數 \(E_i\)

輸出格式

第一行:一個值,表示 Farm John 可以得到的最大的效率值

輸入輸出樣例

輸入 #1

5 2
1
2
3
4
5

輸出 #1

12

思路分析

  • 首先不難想到,每隻奶牛在當前選或者不選都有可能對後續的最佳答案產生影響,所以我們在轉移時完全可以將這兩個狀態分開來存,即開一個二維

    \(f\)陣列。

  • 另外像這種需要連續選物品的\(DP\),使用字首和陣列會有奇效

  • 定義\(f[i][0/1]\)陣列表示到第i頭牛,狀態為\(0/1\)的情況,\(0\)表示不選,\(1\)表示選。那麼兩種狀態的轉移方程就可以得出:

    • 對於不選的情況,我們直接從上一個轉移即可,即:\(f[i][0] = max(f[i-1][1],f[i-1][0])\)
    • 選了的情況相對複雜,我們讓當前這頭牛為一連串牛的結尾,遍歷起點,則有:\(f[i][1] = max(f[i][1],f[j-1][0]+sum[i]-sum[j-1])\);

    信心滿滿的交上去,70分T掉

  • 顯然,這樣的轉移方程在一串牛的長度很長時時間效率會很低,所以考慮優化

  • 仔細看一看\(f[i][1]\)的這個轉移方程,我們是以\(i\)為終點尋找一個最優起點,那最最優的起點應該有兩條性質:

    1. 效率值大
    2. 位置靠後(這樣才可以形成更長的序列)
  • 根據上面的性質,對於一些兩個都不符合的點,顯然就沒有機會再選了,要想對後面的情況產生貢獻,最起碼得佔一個,顯然要用單調佇列來維護,這也是單調佇列的核心思想:人家比你小還比你強,憑什麼選你

  • 根據上面的轉移方程,因為\(sum[i]\)是定值,所以用佇列維護\(f[j-1][0]-sum[j-1]\)就可以了(別把字首和丟了)

Code

#include<cstdio>
#include<cstring>
#include<cmath>
#include<algorithm>
#define N 100010
#define ll long long
using namespace std;
inline ll read(){
	ll x = 0,f = 1;
	char ch =getchar();
	while(ch>'9'||ch<'0'){if(ch=='-')f=-1;ch=getchar();}
	while(ch>='0'&&ch<='9'){x=(x<<1)+(x<<3)+(ch^48);ch=getchar();}
	return x * f;
}
ll n,k;
ll f[N][2],e[N],sum[N],q[N];
int main(){
	n = read(),k = read();
	for(int i = 1;i <= n;i++){
		e[i] = read();
		sum[i] = sum[i-1] + e[i];
	}
	int head = 0,tail = 1;//注意隊首為0,要不你會預設最開始1不選是最優的
	q[tail] = 1;
	f[1][1] = e[1];
	for(int i = 2;i <= n;i++){
		while(head<=tail && i-q[head] > k)head++;
		f[i][1] = f[q[head]][0] + sum[i] - sum[q[head]];//既然在隊首沒被彈掉,肯定是最大的啦
		f[i][0] = max(f[i-1][0],f[i-1][1]);//這個直接轉移就行
		while(head<=tail && f[i][0]-sum[i]>=f[q[tail]][0]-sum[q[tail]])tail--;
		q[++tail] = i;
	}
	printf("%lld\n",max(f[n][0],f[n][1]));
	return 0;
}