从例题开始
Solution
首先,状态设计十分显然: d p i dp_i dpi表示前 i i i个数的答案。
状态转移也十分显然: d p i = d p l − 1 + ( ∑ j = l i a j ) 2 + M dp_i=dp_{l-1}+(\sum_{j=l}^i a_j)^2+M dpi=dpl−1+(∑j=liaj)2+M。
即使使用了前缀和来优化,时间复杂度也仍只有 O ( n 2 ) O(n^2) O(n2),无法接受。
定义 d p i dp_i dpi的决策点为使得 d p i dp_i dpi的值最小的 j j j,珂以发现,当 i i i的值变大的同时, d p i dp_i dpi的决策点竟然单调不减。
我们称这个性质为“决策单调性”。
这个状态转移具有决策单调性又有什么用呢?难道可以优化到 O ( n l o g n ) O(nlogn) O(nlogn)? 是的,我们可以这么优化:
定义一个数组 p p p, p i p_i pi表示 d p i dp_i dpi的决策点。当我们想要求出 d p i dp_i dpi的时候,我们先根据 p i p_i pi的值迅速转移得到 d p i dp_i dpi;然后我们从末尾往前扫一遍这个数组 p p p,如果对于一个 j j j使得 p j p_j pj作为决策点没有 i i i作为决策点更优,那么就把这个 p j p_j pj替换掉。根据决策拥有单调性,我们可以优化这个扫描 p p p数组并尝试替换的步骤,直接大力二分,得到 x x x及其之后的决策点是 i i i更优,然后我们将 p p p数组中 [ x , n ] [x,n] [x,n]这段区间全部替换为 i i i即可。
这里涉及到“二分+单点查询,与区间摊”,可以使用线段树来维护,时间复杂度 O ( n l o g 2 n ) O(nlog^2n) O(nlog2n)。
能不能优化到 O ( n l o g n ) O(nlogn) O(nlogn)呢?
我们学习一下珂朵莉树的思想(这是体现珂朵莉可爱的时候啦 ),我们维护许多三元组。一个三元组为
(
l
,
r
,
x
)
(l,r,x)
(l,r,x),表示
p
l
p_l
pl到
p
r
p_r
pr目前的决策点是
x
x
x。
每次我们:
①转移得到
d
p
i
dp_i
dpi,这个步骤没有变化。
②去掉开头无用的三元组。
即,假设我们扫描到的
i
i
i为
4
4
4,而最左边的那个三元组是
(
4
,
6
,
2
)
(4,6,2)
(4,6,2),可以发现
“
4
”
“4”
“4”在做完①中的转移后就没用了,那么我们就将这个三元组变成
(
5
,
6
,
2
)
(5,6,2)
(5,6,2)。还有一种情况,就是这个三元组是
(
4
,
4
,
1
)
(4,4,1)
(4,4,1),这时整个三元组都没用了,直接去掉即可。
③去掉末尾无用的三元组。我们从末尾往前扫,假设目前扫描到的三元组的开头是 l l l,而 l l l作为决策点没有 i i i作为决策点更优,那么我们就直接把这个三元组删掉。
为什么可以删呢?为什么我们只需要判断左端点就可以了呢? 因为,在看到 i i i的这一时刻,所有三元组的第三个元素的值都不会达到 i i i。即,对于一个三元组,如果对于三元组的一个 l l l, i i i作为决策点更优,那么整个三元组的决策点一定会不小于 i i i,而绝对不可能是任何小于 i i i的数。原来的决策点可以作废了,这个区间删掉就好了。
④我们可能会出现这样一种情况:
⌊ \lfloor ⌊ 一个三元组表示的一段区间中,前面的一部分的决策点不变,后面的那一部分的决策点是 i i i更优。 ⌉ \rceil ⌉
对于这样子的区间,显然有且仅有一个。我们直接在这个区间里面二分一个 m i d mid mid,使得 m i d mid mid左边的所有 d p dp dp值的决策点不变更优, m i d mid mid及其右边的 d p dp dp值的决策点变成 i i i更优。根据决策的单调性,二分的正确性有了保障。
⑤插入一段三元组 ( m i d , n , i ) (mid,n,i) (mid,n,i),即区间 [ m i d , n ] [mid,n] [mid,n]的决策点是 i i i。
放几张图:
At first:
①根据第一个三元组的决策点转移
②去掉无用的
③从末尾往前扫,假设当前三元组的左端点是
l
l
l,区间决策点为
p
o
s
pos
pos;而
i
i
i作为决策点比
p
o
s
pos
pos更佳。对于这样的区间直接删掉。
④我们在当前三元组序列末尾的区间里面二分一个
m
i
d
mid
mid,使得
m
i
d
mid
mid左边的所有
d
p
dp
dp值的决策点不变更优,
m
i
d
mid
mid及其右边的
d
p
dp
dp值的决策点变成
i
i
i更优。
⑤插入一段三元组
(
m
i
d
,
n
,
i
)
(mid,n,i)
(mid,n,i),即区间
[
m
i
d
,
n
]
[mid,n]
[mid,n]的决策点是
i
i
i。
时间复杂度
O
(
n
l
o
g
n
)
O(nlogn)
O(nlogn)。
Code
#include <bits/stdc++.h>
#define int long long
using namespace std;
int n,m,l=1,r=1;
int a[500005],pre[500005],dp[500005];
struct DP_triples
{
int l,r,pos;
}b[500005];
int cost(int l,int r)
{
return dp[l]+(pre[r]-pre[l])*(pre[r]-pre[l])+m;
}
int Binary(int l,int r,int i,int j)//二分那个mid
{
int p;
while (l<=r)
{
int mid=(l+r)>>1;
if (cost(i,mid)<=cost(j,mid))
{
p=mid;
r=mid-1;
}
else l=mid+1;
}
return p;
}
inline int read()
{
int s=0,w=1;
char ch=getchar();
while (ch<'0'||ch>'9')
{
if (ch=='-') w=-w;
ch=getchar();
}
while (ch>='0'&&ch<='9')
{
s=(s<<1)+(s<<3)+(ch^'0');
ch=getchar();
}
return s*w;
}
signed main()
{
while (~scanf("%lld%lld",&n,&m))
{
for (int i=1;i<=n;i++) a[i]=read();
for (int i=1;i<=n;i++) pre[i]=pre[i-1]+a[i];
l=1,r=1;
b[l].l=1,b[l].r=n,b[l].pos=0;
for (int i=1;i<=n;i++)
{
dp[i]=cost(b[l].pos,i);
if (b[l].r==i) l++;
else b[l].l++;
while (cost(b[r].pos,b[r].l)>=cost(i,b[r].l)) r--;
if (l>r)
{
r++;
b[r].l=i+1,b[r].r=n,b[r].pos=i;
}
else
{
int k;
if (cost(b[r].pos,b[r].r)<=cost(i,b[r].r)) k=b[r].r+1;
else k=Binary(b[r].l,b[r].r,i,b[r].pos);
if (k<=n)
{
b[r].r=k-1;
b[++r].l=k,b[r].r=n,b[r].pos=i;
}
}
}
cout<<dp[n]<<endl;
}
return 0;
}
注意事项(特别重要!)
回顾一下上面我们所说的几步走,里面的特判特别多。
①直接转移: 没啥特判。就算有特判,也与“决策单调性优化
d
p
dp
dp”本身无关。
②去掉开头无用的: 一定要注意两种情况: 左端点加
1
1
1,与整个三元组都要删去。
if (b[l].r==i) l++;
else b[l].l++;
③去掉末尾错误的: 如果把整个三元组序列删成空的了,一定要补上一个 ( i + 1 , n , i ) (i+1,n,i) (i+1,n,i)并不再二分。
if (l>r)
{
r++;
b[r].l=i+1,b[r].r=n,b[r].pos=i;
}
else 二分
④二分: 特判一下整个区间的决策点都不变的情况
if (cost(b[r].pos,b[r].r)<=cost(i,b[r].r)) k=b[r].r+1;
⑤加入区间: 特判一下 m i d mid mid(即代码中的 k k k)不小于 n n n的情况。这种情况出现,当且仅当 i i i不能成为后面任何区间的更优的决策点。
if (k<=n)
{
b[r].r=k-1;
b[++r].l=k,b[r].r=n,b[r].pos=i;
}
顺便发一句牢骚,这个东西为什么叫二分栈啊……
即:
①转移;
②改头;
③删尾;
④二分;
⑤插入。
②③④⑤步各有一个特判,请注意。
模板题
练习题
这题不是 d p dp dp题,但是有决策单调性,是不是很有意思……