1. 程式人生 > >[BZOJ2684][CEOI2004]鋸木廠選址

[BZOJ2684][CEOI2004]鋸木廠選址

isp 需要 gpo bzoj esc clas getchar() ref names

BZOJ權限題!

Description

從山頂上到山底下沿著一條直線種植了n棵老樹。當地的政府決定把他們砍下來。為了不浪費任何一棵木材,樹被砍倒後要運送到鋸木廠。
木材只能按照一個方向運輸:朝山下運。山腳下有一個鋸木廠。另外兩個鋸木廠將新修建在山路上。你必須決定在哪裏修建兩個鋸木廠,使得傳輸的費用總和最小。假定運輸每公斤木材每米需要一分錢。

Input

輸入的第一行為一個正整數n——樹的個數(2≤n≤20 000)。樹從山頂到山腳按照1,2……n標號。接下來n行,每行有兩個正整數(用空格分開)。第i+1行含有:wi——第i棵樹的重量(公斤為單位)和 di——第i棵樹和第i+1棵樹之間的距離,1≤wi ≤10 000,0≤di≤10 000。最後一個數dn,表示第n棵樹到山腳的鋸木廠的距離。保證所有樹運到山腳的鋸木廠所需要的費用小於2000 000 000分。

Output

輸出只有一行一個數:最小的運輸費用。

Sample Input

9
1 2
2 1
3 3
1 1
3 2
1 6
2 1
1 2
1 1

Sample Output

26

sol

首先寫出\(O(n^2)\)\(DP\)式,直接枚舉兩個鋸木廠的選址(下面假設\(d_i\)表示\(i\)到鋸木廠的距離)
\[f_i=min\{\sum_{x=1}^{j}(d_x-d_j)w_x+\sum_{x=j+1}^{i}(d_x-d_i)w_x+\sum_{x=i+1}^{n}d_xw_x\}(j<i)\]
\(s_i\)\(w_i\)的前綴和
\[f_i=\sum_{x=1}^{n}d_xw_x-max\{d_js_j+d_i(s_i-s_j)\}(j<i)\]


\(k<j\)\(j\)\(k\)
\[d_js_j+d_i(s_i-s_j)>d_ks_k+d_i(s_i-s_k)\]
移項得
\[\frac{d_js_j-d_ks_k}{s_j-s_k}>d_i\]

所以這裏維護一個下凸殼,即斜率遞減

code

#include<cstdio>
#include<algorithm>
using namespace std;
#define ll long long
const int N = 100005;
ll gi()
{
    ll x=0,w=1;char ch=getchar();
    while
((ch<'0'||ch>'9')&&ch!='-') ch=getchar(); if (ch=='-') w=0,ch=getchar(); while (ch>='0'&&ch<='9') x=(x<<3)+(x<<1)+ch-'0',ch=getchar(); return w?x:-x; } int n,q[N],hd,tl; ll w[N],d[N],s[N],Sum,ans=1e18; double calc(int j,int k) { return (double)(d[j]*s[j]-d[k]*s[k])/(s[j]-s[k]); } ll count(int i,int j) { return Sum-d[j]*s[j]-d[i]*(s[i]-s[j]); } int main() { n=gi(); for (int i=1;i<=n;i++) w[i]=gi(),d[i]=gi(); for (int i=n;i>=1;i--) d[i]+=d[i+1]; for (int i=1;i<=n;i++) s[i]=s[i-1]+w[i],Sum+=d[i]*w[i]; /* for (int i=1;i<=n;i++) for (int j=i-1;j;j--) printf("i=%d j=%d res=%lld\n",i,j,count(i,j)); */ for (int i=1;i<=n;i++) { while (hd<tl&&calc(q[hd],q[hd+1])>d[i]) ++hd; ans=min(ans,count(i,q[hd])); while (hd<tl&&calc(q[tl-1],q[tl])<calc(q[tl],i)) --tl; q[++tl]=i; } printf("%lld\n",ans); return 0; }

[BZOJ2684][CEOI2004]鋸木廠選址