1. 程式人生 > >BZOJ3437 小P的牧場(斜率優化dp)

BZOJ3437 小P的牧場(斜率優化dp)

() can sin otto 兩個 奶牛 mage 方法 技術

題目link:http://www.lydsy.com/JudgeOnline/problem.php?id=3437;

略略讀一下題,發現這題是一道dp

有一些牧場:

1 2 3 4 5 6 7 8 9 10

其中編號大的可以管住編號小的.

a[i]表示建站費用 b[i]表示養殖奶牛數目

dp方程大概就出來了

f[i]=min(f[j]+cost(j+1,i)) (0<j<i);

可是有些問題需要O(1)計算cost(j+1,i);

怎麽辦呢?

於是我想了一個很不清真的計算方法

開了兩個數組

形式化的講:

sumb[i]=sumb[i-1]+b[i]

sum[i]=sum[i-1]+sumb[i-1]

有什麽意義呢

sumb[i]表示b的前綴和

sum[i]表示如果1到i-1都要由i控制的運輸費用;(不計建站費用)

第一個轉移是顯然的

第二個轉移是因為可以將運輸想象成兩部分,先運到i-1,再運到i

然後就可以O(1)計算cost(j+1,i)了

cost(j+1,i)=sum[i]-sum[j]-sumb[j]*(i-j)

這個文字比較難解釋,還是看圖吧

矩形的長指i-k

矩形的寬指b[k]

(j<k<i)

則矩形的面積指該點對sum的貢獻

技術分享

很顯然,現在cost=3號區域的面積

已知:總面積=sum[i]; 1號區域面積=sum[j]; 2號區域面積=sumb[j]*(i-j);

所以cost(j+1,i)=sum[i]-sum[j]-sumb[j]*(i-j);

/-----------------分割線-----------------/

現在f[i]=min(f[j]+sum[i]-sum[j]-sumb[j]*(i-j)+a[i])

f[i]=min(f[j]-sum[j]-sumb[j]*(i-j))+sum[i]+a[i];

開始斜率優化!

1.去掉min,確認是維護下凸包

2.f[i]=f[j]-sum[j]-sumb[j]*(i-j)+sum[i]+a[i];

3.化式子f[i]+i*sumb[j]=f[j]-sum[j]+sumb[j]*j+const(這一坨是跟j無關的常量)

4.對應 b +k x =y

然後一切就簡單了

但是這題有坑點,a,b數組要開long long,題中的數據範圍是假的

我因為括號寫錯了位置,WA一上午,最後只改了一個字符就AC了!QAQ

AC代碼:

#include<bits/stdc++.h>
using namespace std;
const int N=1000010;
int n,h,t,q[N];
long long a[N],b[N],sum[N],sumb[N],f[N];
inline double X(const int &j){
    return sumb[j];
}
inline double Y(const int &j){
    return f[j]-sum[j]+sumb[j]*j;
}
inline double Rate(const int i,const int j){
    return (Y(i)-Y(j))/(X(i)-X(j));
}
int main(){
    scanf("%d",&n);
    for (int i=1; i<=n; i++) scanf("%lld",&a[i]);
    for (int i=1; i<=n; i++) scanf("%lld",&b[i]);
    for (int i=1; i<=n; i++){
        sumb[i]=sumb[i-1]+b[i];
        sum[i]=sum[i-1]+sumb[i-1];
    }
    for (int i=1; i<=n; i++){
        while (h<t&&Rate(q[h],q[h+1])<i) ++h;
        f[i]=f[q[h]]+sum[i]-sum[q[h]]-sumb[q[h]]*(i-q[h])+a[i];
           while(h<t&&Rate(q[t-1],q[t])>Rate(q[t],q[i])) t--;
        q[++t]=i;
    }
    printf("%lld",f[n]);
}

不知道讀者明白這題了沒有,不明白的話請聯系Yuhuger

BZOJ3437 小P的牧場(斜率優化dp)