[BZOJ2684][CEOI2004]鋸木廠選址
阿新 • • 發佈:2018-01-22
isp 需要 gpo bzoj esc clas getchar() ref names
設\(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\]
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]鋸木廠選址