[USACO]Land Acquisition G「斜率優化DP」
阿新 • • 發佈:2020-08-16
[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; }