1. 程式人生 > 實用技巧 >[USACO]Land Acquisition G「斜率優化DP」

[USACO]Land Acquisition G「斜率優化DP」

[USACO]Land Acquisition G「斜率優化DP」

題目描述

Farmer John 準備擴大他的農場,眼前他正在考慮購買 \(N\) 塊長方形的土地。

如果 FJ 單買一塊土地,價格就是土地的面積。但他可以選擇併購一組土地,併購的價格為這些土地中最大的長乘以最大的寬。比如 FJ 併購一塊 \(3×5\) 和一塊 \(5×3\) 的土地,他只需要支付 \(5×5=25\) 元, 比單買合算。

FJ 希望買下所有的土地。他發現,將這些土地分成不同的小組來併購可以節省經費。 給定每份土地的尺寸,請你幫助他計算購買所有土地所需的最小費用。

輸入格式

第一行一個整數 \(N\)\(1≤N≤5×10^4\)

)。

接下來 \(N\) 行,每行兩個整數 \(w_i\)\(l_i\),代表第 \(i\) 塊土地的長和寬。保證土地的長和寬不超過 \(10^6\)

輸出格式

輸出買下所有土地的最小費用。

輸入輸出樣例

輸入 #1

4 
100 1 
15 15 
20 5 
1 100 

輸出 #1

500 

思路分析

  • 顯然如果直接按原序列下手會很棘手,所以我們首先要將序列排好序
    • 為了方便合併,所以我們以長為關鍵字進行排序,這時候好像還是有點無從下手,所以嘗試找一些性質
    • 不難想到,如果一塊土地的長和寬均小於某一塊土地,那這塊土地其實是不會對答案產生任何影響的,所以我們在排好序以後可以將其去除,而只留下合併時會改變長寬的
    • 在進行完的排序和去除操作以後,這時候的序列一定滿足這樣的性質:長是遞減的,而寬是遞增的(自己模擬一下就好)
  • 那麼此時,對於一段區間,我們只需要取兩端即可,因為一個長最大,一個寬最大,區間內的自然就一起合併了
    • 由此得出轉移方程: \(f[i] = min(f[i],f[j-1]+x_j*y_i)\) \((0<j<i)\)(\(x\) 為長,\(y\) 為寬)
    • 變一下型就是 \(f[j-1]=f[i]-y_i*x_j\),這樣就是一個很簡單的可以進行斜率優化的式子了,斜率為 \(y_i\)

Code

#include<cstdio>
#include<cstring>
#include<cmath>
#include<algorithm>
#define N 50010
using namespace std;
inline int read(){
	int 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;
}
int n,q[N];
long long f[N];
struct farm{
	int x,y;
}a[N];
inline bool cmp(farm a,farm b){
	return a.x==b.x ? a.y>b.y : a.x>b.x;
}
double slope(int x,int y){
	return (double)(f[x]-f[y])/(double)(a[y+1].x-a[x+1].x);
}
int main(){
	n = read();
	for(int i = 1;i <= n;i++){
		a[i].x = read(),a[i].y = read();
	}
	sort(a+1,a+1+n,cmp);
	int tot = 0;
	for(int i = 1;i <= n;i++)if(a[i].y>a[tot].y)a[++tot] = a[i]; //去除操作
	n = tot;
	int head = 0,tail = 0;
	for(int i = 1;i <= n;i++){ //按轉移方程直接上斜率優化即可
		while(head<tail&&slope(q[head],q[head+1])<=a[i].y)head++;
		f[i] = f[q[head]]+1ll*a[q[head]+1].x*a[i].y;
		while(head<tail&&slope(q[tail-1],q[tail])>=slope(q[tail],i))tail--;
		q[++tail] = i;
	}
	printf("%lld",f[n]);
	return 0;
}