題意
有一篇文章,每個字有一個權值,文章中每行的花費爲這行上面所有數字之和的平方再加上一個常數m,要求整篇文章的最小花費。
思路
首先它是個dp題目,我們先把轉移方程寫出來
沒有優化的做法
沒有優化,直接做,可以知道時間複雜度是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;
}
使用斜率優化
假設 k < j < i並且從 dp[j] 轉移到 dp[i] 比從 dp[k] 轉移到 dp[i] 更優
那麼就有
進行化簡有
這個時候,左邊是斜率,右邊是一個值
符合該條件時,假設成立。
也就等價於一個平面上求兩點的斜率,當兩點斜率小於等於 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;
}