有长度为 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;
}