题目链接
题意
将n个数任意划分成若干区间,每个区间贡献为,区间内权值和的平方+m,求最小的贡献和是多少。
思路
斜率DP入门题。
设
d
p
[
前
i
个
最
小
贡
献
]
dp[前i个最小贡献]
dp[前i个最小贡献],
p
r
e
[
前
i
个
元
素
前
缀
和
]
pre[前i个元素前缀和]
pre[前i个元素前缀和] 快速求区间和用
最简单的DP思路,
d
p
[
i
]
=
∑
j
=
1
i
−
1
m
i
n
(
d
p
[
j
]
+
(
p
r
e
[
i
]
−
p
r
e
[
j
]
)
2
+
m
)
dp[i] = \sum_{j=1}^{i-1}min(dp[j]+(pre[i]-pre[j])^2+m)
dp[i]=∑j=1i−1min(dp[j]+(pre[i]−pre[j])2+m),
O
(
n
2
)
O(n^2)
O(n2) 显然超时。
设 k 1 > k 2 k1>k2 k1>k2,在 k 1 k1 k1 处转移比 k 2 k2 k2 处转移更优
得 d p [ k 1 ] + ( p r e [ i ] − p r e [ k 1 ] ) 2 + m > d p [ k 2 ] + ( p r e [ i ] − p r e [ k 2 ] ) 2 + m dp[k1]+(pre[i]-pre[k1])^2+m>dp[k2]+(pre[i]-pre[k2])^2+m dp[k1]+(pre[i]−pre[k1])2+m>dp[k2]+(pre[i]−pre[k2])2+m
化简移项 d p [ k 1 ] + p r e [ k 1 ] 2 − d p [ k 2 ] − p r e [ k 2 ] 2 p r e [ k 1 ] − p r e [ k 2 ] < 2 p r e [ i ] \frac{dp[k1]+pre[k1]^2-dp[k2]-pre[k2]^2}{pre[k1]-pre[k2]}<2pre[i] pre[k1]−pre[k2]dp[k1]+pre[k1]2−dp[k2]−pre[k2]2<2pre[i]
写不动了下面简略写。具体学习链接
维护一个下凸的斜率,由于
p
r
e
[
i
]
pre[i]
pre[i] 单调增,斜率只要留一个小于当前更新到的
2
∗
p
r
e
[
i
]
2*pre[i]
2∗pre[i] 即可
代码
#include <bits/stdc++.h>
using namespace std;
#define ll long long
ll read(ll &_a){return scanf("%lld",&_a);}
ll rd(){ll _tmp; scanf("%lld",&_tmp); return _tmp;}
ll pre[500005], dp[500005], que[500005];
ll getd(ll i, ll j, ll ii, ll jj)
{
return (dp[i]+pre[i]*pre[i]-dp[j]-pre[j]*pre[j])*(pre[ii]-pre[jj]) >= (dp[ii]+pre[ii]*pre[ii]-dp[jj]-pre[jj]*pre[jj])*(pre[i]-pre[j]);
}
ll getd(ll i, ll j, ll num)
{
return (dp[j]+pre[j]*pre[j]-dp[i]-pre[i]*pre[i]) <= num*(pre[j]-pre[i]);
}
int main()
{
ll n, m;
while(~scanf("%lld%lld",&n,&m))
{
ll head = 0, tail = 0;
que[0] = 0;
// 初始插个斜率为0的进去,使转移方程可以从头开始
for(ll i = 1; i <= n; ++i)
{
read(pre[i]), pre[i] += pre[i-1];
ll k1 = que[head], k2 = que[head+1];
while(head < tail && getd(k1,k2,pre[i]*2ll) ) ++head, k1 = que[head], k2 = que[head+1];
dp[i] = dp[k1]+(pre[i]-pre[k1])*(pre[i]-pre[k1])+m;
while(head < tail && getd(que[tail],que[tail-1],i,que[tail]) ) --tail;
que[++tail] = i;
}
printf("%lld\n",dp[n]);
}
return 0;
}