HDU 3507 Print Article(斜率優化DP)

題意

有一篇文章,每個字有一個權值,文章中每行的花費爲這行上面所有數字之和的平方再加上一個常數m,要求整篇文章的最小花費。

思路

首先它是個dp題目,我們先把轉移方程寫出來
dp[i]=dp[j]+(sum[i]sum[j])2+M(0<j<i)dp[i] = dp[j] + (sum[i] - sum[j])^2 + M (對於所有 0 < j < i )

沒有優化的做法
沒有優化,直接做,可以知道時間複雜度是O(n2)的,而 n <= 105 會超時。

代碼如下:

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int maxn = 5e5 + 10;
const ll INF = 0x3f3f3f3f3f3f3f3f;
ll a[maxn], sum[maxn], dp[maxn];
int main()
{

    ll n, m;
    scanf("%lld %lld", &n, &m);
    for(int i = 1; i <= n; i++){
        scanf("%lld", &a[i]);
        sum[i] = sum[i - 1] + a[i];
    }
    memset(dp, INF, sizeof(dp));
    dp[1] = sum[1] * sum[1] + m;
    for(int i = 2; i <= n; i++){
        for(int j = 1; j < i; j++){
            if(dp[i] > dp[j] + pow(sum[i] - sum [j], 2) + m)
                dp[i] = dp[j] + pow(sum[i] - sum [j], 2) + m;
        }
    }
    printf("%lld\n", dp[n]);
    return 0;
}

使用斜率優化
dp[i]=dp[j]+(sum[i]sum[j])2+M(0<j<i)dp[i] = dp[j] + (sum[i] - sum[j])^2 + M (對於所有 0 < j < i )

假設 k < j < i並且從 dp[j] 轉移到 dp[i] 比從 dp[k] 轉移到 dp[i] 更優

那麼就有
dp[j]+(sum[i]sum[j])2+M<dp[k]+(sum[i]sum[k])2+Mdp[j] + (sum[i] - sum[j])^2 + M < dp[k] + (sum[i] - sum[k])^2 + M
進行化簡有
(dp[j]+sum[j]2)(dp[k]+sum[k]2)2(sum[j]sum[k])<sum[i]\frac{(dp[j] + sum[j]^2) - (dp[k] + sum[k]^2)}{2 * (sum[j] - sum[k])} < sum[i]
這個時候,左邊是斜率,右邊是一個值
y[j]y[k]x[j]x[k]<a\frac{y[j] - y[k]}{x[j] - x[k]} < a

符合該條件時,假設成立。

也就等價於一個平面上求兩點的斜率,當兩點斜率小於等於 ai 時要轉移。

那麼對於斜率大於ai的兩點,我們把它略去,顯然這是在維護一個斜率的下凸包。

例如下圖
在這裏插入圖片描述

所以,我們一開始先求一下sum數組,然後維護斜率的下凸包,最後一邊跑dp一邊求y數組即可。

維護斜率的下凸包用一個隊列que[]。

時間複雜度爲O(n)
代碼如下:

#include <cstdio>
#include <cstdlib>
using namespace std;
typedef long long ll;
const int maxn = 5e5 + 10;
ll a[maxn];
ll sum[maxn];
ll dp[maxn];
ll que[maxn];
ll get_up(int u, int v)///Y
{
    return (dp[u] + sum[u] * sum[u]) - (dp[v] + sum[v] * sum[v]);///視情況修改
}
ll get_down(int u, int v)///x
{
    return 2 * (sum[u] - sum[v]);///視情況修改
}
int main()
{
    ll n, m;
    while(scanf("%lld %lld", &n, &m) != EOF)
    {
        for(int i = 1; i <= n; i++)
        {
            scanf("%lld", &a[i]);
            sum[i] = sum[i - 1] + a[i];
        }
        int L = 1, R = 1;
        for(int i = 1; i <= n; i++)
        {
            //如果斜率小於右側ai,那麼向前轉移
            while(L < R && get_up(que[L + 1], que[L]) <= sum[i] * get_down(que[L + 1], que[L]))
                L++;
            //求出dp的值
            dp[i] = dp[que[L]] + (sum[i] - sum[que[L]]) *(sum[i] - sum[que[L]]) + m;
            //維護下凸性
            while(L < R && get_up(i, que[R]) * get_down(que[R], que[R - 1]) <= get_up(que[R], que[R - 1]) * get_down(i, que[R]))
                R--;
            que[++R] = i;
        }
        printf("%lld\n", dp[n]);
    }
    return 0;
}
發佈了184 篇原創文章 · 獲贊 24 · 訪問量 2萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章