1. 程式人生 > 實用技巧 >[CF319C] Kalila and Dimna in the Logging Industry - 斜率優化dp

[CF319C] Kalila and Dimna in the Logging Industry - 斜率優化dp

Description

砍伐高度為 \(a_1,a_2,...,a_n\)\(n\) 棵樹。每次他們對編號為 \(i\) 的樹使用電鋸,會使第 \(i\) 個樹的高度降低 \(1\)。每次使用電鋸後需要給它充電。充電成本取決於已完全鋸掉的樹木的編號(樹木高度等於 \(0\) 時,我們說樹木被完全鋸掉 )。如果已經被完全鋸掉的樹的最大編號是 \(i\) ,則對電鋸充電一次的成本將是 \(b_i\) 。電鋸在開始時是充好電的。保證對於 \(a,b\) 的嚴格單調性以及 \(b_n = 0,a_1 = 1\) 。要以最低成本完全鋸掉所有的樹,計算這個最小代價。

Solution

只要我們鋸掉第 \(n\)

棵樹那麼剩下的部分代價都是 \(0\),因此我們只需要考慮鋸掉第 \(n\) 棵樹的最小代價

\(f[i]\) 表示鋸掉第 \(i\) 棵樹的最小代價,則有

\[f[i]=\min_{1\le i <j } (f[i]+a[i]b[j]) \]

對於 \(j \le k\),若 \(k\) 優於 \(j\) 則有 \(f[j]+a[i]b[j] > f[k]+a[i]b[k]\),化為斜率形式為

\[\frac {(-f[k])-(-f[j])} {b[k]-b[j]} < a[i] \]

\(a[i]\) 單調遞增,因此我們用單調佇列維護下凸包即可

#include <bits/stdc++.h>
using namespace std;

#define int long long
const int N = 1000005;

int n,a[N],b[N],f[N],q[N],head,tail;

// Slope DP Utilities

int getx(int i)
{
    return b[i];
}

int gety(int i)
{
    return -f[i];
}

int getk(int i)
{
    return a[i];
}

double getslope(int j,int k)
{
    return 1.0 * (gety(k)-gety(j)) / (getx(k)-getx(j));
}

signed main()
{
    ios::sync_with_stdio(false);
    cin>>n;
    for(int i=1;i<=n;i++) cin>>a[i];
    for(int i=1;i<=n;i++) cin>>b[i];

    // Push (1) into the queue
    head=tail=0;
    q[0]=1;

    for(int i=2;i<=n;i++)
    {
        while(head<tail && getslope(q[head],q[head+1]) <= getk(i))
        {
            ++head;
        }
        f[i] = f[q[head]] + a[i]*b[q[head]];
        while(head<tail && getslope(q[tail-1],q[tail]) >= getslope(q[tail],i))
        {
            --tail;
        }
        q[++tail] = i;
    }

    cout<<f[n]<<endl;
}