题意
有一篇文章,每个字有一个权值,文章中每行的花费为这行上面所有数字之和的平方再加上一个常数m,要求整篇文章的最小花费。
思路
首先它是个dp题目,我们先把转移方程写出来
d
p
[
i
]
=
d
p
[
j
]
+
(
s
u
m
[
i
]
−
s
u
m
[
j
]
)
2
+
M
(
对
于
所
有
0
<
j
<
i
)
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;
}
使用斜率优化
d
p
[
i
]
=
d
p
[
j
]
+
(
s
u
m
[
i
]
−
s
u
m
[
j
]
)
2
+
M
(
对
于
所
有
0
<
j
<
i
)
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] 更优
那么就有
d
p
[
j
]
+
(
s
u
m
[
i
]
−
s
u
m
[
j
]
)
2
+
M
<
d
p
[
k
]
+
(
s
u
m
[
i
]
−
s
u
m
[
k
]
)
2
+
M
dp[j] + (sum[i] - sum[j])^2 + M < dp[k] + (sum[i] - sum[k])^2 + M
dp[j]+(sum[i]−sum[j])2+M<dp[k]+(sum[i]−sum[k])2+M
进行化简有
(
d
p
[
j
]
+
s
u
m
[
j
]
2
)
−
(
d
p
[
k
]
+
s
u
m
[
k
]
2
)
2
∗
(
s
u
m
[
j
]
−
s
u
m
[
k
]
)
<
s
u
m
[
i
]
\frac{(dp[j] + sum[j]^2) - (dp[k] + sum[k]^2)}{2 * (sum[j] - sum[k])} < sum[i]
2∗(sum[j]−sum[k])(dp[j]+sum[j]2)−(dp[k]+sum[k]2)<sum[i]
这个时候,左边是斜率,右边是一个值
y
[
j
]
−
y
[
k
]
x
[
j
]
−
x
[
k
]
<
a
\frac{y[j] - y[k]}{x[j] - x[k]} < a
x[j]−x[k]y[j]−y[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;
}