土地購買 (斜率優化dp)
土地購買 (斜率優化dp)
題目描述
農夫 \(John\) 準備擴大他的農場,他正在考慮$ N(1 \leqslant N \leqslant 50,000)$ 塊長方形的土地. 每塊土地的長寬滿足\((1 \leqslant\) 寬 \(\leqslant 1,000,000; 1 \leqslant\) 長 \(\leqslant 1,000,000)\).
每塊土地的價格是它的面積,但 \(FJ\) 可以同時購買多塊土地. 這些土地的價格是它們最大的長乘以它們最大的寬, 但是土地的長寬不能交換. 如果 \(FJ\) 買一塊 \(3\times 5\) 的地和一塊 \(5\times 3\)的地,則他需要付 \(5\times 5=25\)
\(FJ\) 希望買下所有的土地,但是他發現分組來買這些土地可以節省經費. 他需要你幫助他找到最小的經費.
輸入格式
第 \(1\) 行:一個數: \(N\)
第 \(2..N+1\) 行:第 \(i+1\) 行包含兩個數,分別為第 \(i\) 塊土地的長和寬。
輸出格式
第一行: 最小的可行費用。
樣例
樣例輸入
4
100 1 15 15 20 5 1 100
樣例輸出
500
樣例解釋
共有 \(4\) 塊土地,\(FJ\) 分 \(3\) 組買這些土地: 第一組: \(100\times 1\), 第二組 \(1\times 100\) , 第三組 \(20\times 5\) 和 \(15\times 15\)
資料範圍與提示
對於 \(50\%\) 的資料 \(n \leqslant 1000\)
對於 \(100\%\) 的資料 \(n \leqslant 50000\)
資料有一定梯度
分析
題目中說選一堆土地的時候,只用最大的長和寬計算價值,那麼我們可以先去重,也就是讓包含的關係先處理掉,按照長度從小到大排序,利用一個單調棧來維護單調減的一堆序列,這樣就去完了重(所謂的去重),這時候長度就是遞增,寬度遞減,那麼我們只需要取一段區間的端點即可,轉化成狀態轉移方程就是:
\[f[i]\ =\ min(f[i],f[j-1] + len_j \times width_i) \]
轉化一下柿子,變成 \(f[j-1] = f[i] - len_j \times width_i\)
最後這個式子就可以用來斜率優化了。
程式碼
#include<cstdio>
#include<algorithm>
#include<iostream>
#include<cstring>
#include<cmath>
using namespace std;
const int L=1<<20;
char buffer[L],*S,*T;
#define lowbit(x) (x & -x)
#define getchar() (S==T&&(T=(S=buffer)+fread(buffer,1,L,stdin),S==T)?EOF:*S++)
#define inline __inline__ __attribute__((__always_inline__))
#define max(a,b) (a>b?a:b)
#define re register
#define ll long long
const int maxn = 5e4+10;
ll f[maxn];
struct Node{
ll w,l;
}jl[maxn];
ll staw[maxn];
int top;
ll stal[maxn];
ll q[maxn];
ll a[maxn],b[maxn];
inline ll read(){
int s = 0,f = 1;
char ch = getchar();
while(!isdigit(ch)){
if(ch == '-')f = -1;
ch = getchar();
}
while(isdigit(ch)){
s = s * 10 + ch - '0';
ch = getchar();
}
return s * f;
}
inline bool cmp(Node a,Node b){//排序
if(a.l == b.l)return a.w < b.w;
return a.l < b.l;
}
inline ll calc(int a,int b){//計算函式值
return f[b] + staw[b+1] * stal[a];
}
inline bool jud(int x1,int x2,int x3){//計算斜率
return (b[x3] - b[x1]) * (a[x2] - a[x1]) >= (b[x2] - b[x1]) * (a[x3] - a[x1]);
}
signed main(){
freopen("A.in","r",stdin);
freopen("A.out","w",stdout);
re ll n = read();
for(re int i = 1;i <= n;++i){
jl[i].w = read();
jl[i].l = read();
}
sort(jl+1,jl+n+1,cmp);
for(re int i = 1;i <= n;++i){//單調棧去重,找出包含的情況
while(top && jl[i].w >= staw[top])top--;
staw[++top] = jl[i].w;
stal[top] = jl[i].l;
}
re int head = 1,tail = 1;
a[0] = staw[1];
for(re int i = 1;i <= top;++i){//單調佇列斜率優化
while(head < tail && calc(i,q[head]) >= calc(i,q[head+1]))head++;
f[i] = calc(i,q[head]);
a[i] = staw[i + 1];
b[i] = f[i];
while(head < tail && jud(q[tail-1],q[tail],i))tail--;
q[++tail] = i;
}
printf("%lld\n",f[top]);
return 0;
}