有长度为 n ( 1 ≤ n ≤ 8000 ) n(1\le n\le 8000) n(1≤n≤8000) 的序列 a a a 。将其分为 k k k 个非空连续区间的代价是每个区间内 a i a_i ai 的最大值的总和。对于每个 k ∈ [ 1 , n ] k\in [1,n] k∈[1,n] ,输出最小的代价。
考虑动态规划,设
f
k
,
i
f_{k,i}
fk,i 表示将前
i
i
i 个数字分为
k
k
k 的最小代价。
易得朴素转移式:
f
k
,
i
=
min
x
=
1
i
(
f
k
−
1
,
x
−
1
+
max
y
=
x
i
a
y
)
f_{k,i}=\min_{x=1}^i({f_{k-1,x-1}+\max_{y=x}^i{a_y}})
fk,i=x=1mini(fk−1,x−1+y=xmaxiay)
暴力转移复杂度为
O
(
n
3
)
O(n^3)
O(n3) , 不可行,考虑优化转移复杂度。
不难发现,当第二项
max
y
=
x
i
a
y
\max\limits_{y=x}^i{a_y}
y=xmaxiay 确定时,对
f
k
−
1
,
x
−
1
f_{k-1,x-1}
fk−1,x−1 取最小值即可。
max
y
=
x
i
a
y
\max\limits_{y=x}^i{a_y}
y=xmaxiay 可画出大致图像如下:

该项具有单调性,可以用单调栈维护每一个
max
y
=
x
i
a
y
\max\limits_{y=x}^i{a_y}
y=xmaxiay ,同时维护其对应范围内的
min
f
k
−
1
,
x
−
1
\min{f_{k-1,x-1}}
minfk−1,x−1 即可。
当
i
i
i 增加时,图像可能变化为如下状态:


(绿色为更新的部分,紫色为更新后的有效部分)
我们可以对新的
max
y
=
x
i
a
y
\max\limits_{y=x}^i{a_y}
y=xmaxiay (绿色部分)在弹出单调栈顶元素的过程中用已有的(第二张图红色部分)
min
f
k
−
1
,
x
−
1
\min{f_{k-1,x-1}}
minfk−1,x−1 来更新新的
min
f
k
−
1
,
x
−
1
\min{f_{k-1,x-1}}
minfk−1,x−1 。
设
m
n
i
mn_i
mni 表示
max
y
=
x
i
a
y
=
a
i
\max\limits_{y=x}^i{a_y}=a_i
y=xmaxiay=ai 范围内的
min
f
k
−
1
,
x
−
1
\min{f_{k-1,x-1}}
minfk−1,x−1 。
最终答案应为
f
k
,
i
=
min
(
min
j
∈
s
t
a
c
k
(
m
n
j
+
v
j
)
,
m
n
i
+
a
i
)
f_{k,i}=\min(\min\limits_{j\in stack}({mn_j}+v_j),mn_i+a_i)
fk,i=min(j∈stackmin(mnj+vj),mni+ai) ,然后将
i
i
i 放入单调栈即可。
实际上
f
k
,
t
o
p
=
min
j
∈
s
t
a
c
k
(
m
n
j
+
v
j
)
f_{k,top}=\min\limits_{j\in stack}({mn_j}+v_j)
fk,top=j∈stackmin(mnj+vj) ,故转移式可简化为
f
k
,
i
=
min
(
f
k
,
t
o
p
,
m
n
i
+
a
i
)
f_{k,i}=\min(f_{k,top},mn_i+a_i)
fk,i=min(fk,top,mni+ai) 。
动态规划复杂度为
O
(
n
2
)
O(n^2)
O(n2) ,
n
n
n 次单调栈复杂度也为
O
(
n
2
)
O(n^2)
O(n2) ,可以通过此题。
注意到更新 f k , i f_{k,i} fk,i 只需要 f k − 1 , j f_{k-1,j} fk−1,j ,可以用滚动数组优化内存。
#include
using namespace std;
template<class T>inline void read(T&x){
char c,last=' ';
while(!isdigit(c=getchar()))last=c;
x=c^48;
while(isdigit(c=getchar()))x=(x<<3)+(x<<1)+(c^48);
if(last=='-')x=-x;
}
const int MAXN=8e3+5;
int n;
int a[MAXN];
int mn[MAXN];//范围内最小的f
int stk[MAXN],top;//单调栈
int f[MAXN],g[MAXN];//滚动数组
int main()
{
read(n);
for(int i=1;i<=n;++i)read(a[i]);
memset(g,0x3f,sizeof(g));
g[0]=0;
for(int k=1;k<=n;++k){
f[top=0]=0x3f3f3f3f;//top=0清空栈,同时将f[0]赋值为inf防止空栈时转移出错
for(int i=k;i<=n;++i){
mn[i]=g[i-1];
while(top&&a[stk[top]]<=a[i]){//单调栈
mn[i]=min(mn[i],mn[stk[top]]);//维护mn
--top;
}
f[i]=min(f[stk[top]],mn[i]+a[i]);//转移
stk[++top]=i;//将i压入栈中
}
for(int i=k;i<=n;++i)g[i]=f[i];
cout<<f[n]<<'\n';//答案即f[n]
}
return 0;
}