“蔚来杯“2022牛客暑期多校训练营9 I题: The Great Wall II

I题: The Great Wall II

原题链接:https://ac.nowcoder.com/acm/contest/33194/I

题目大意

有长度为 n ( 1 ≤ n ≤ 8000 ) n(1\le n\le 8000) n(1n8000) 的序列 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(fk1,x1+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} fk1,x1 取最小值即可。
max ⁡ y = x i a y \max\limits_{y=x}^i{a_y} y=xmaxiay 可画出大致图像如下:
“蔚来杯“2022牛客暑期多校训练营9 I题: The Great Wall II_第1张图片
该项具有单调性,可以用单调栈维护每一个 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}} minfk1,x1 即可。
i i i 增加时,图像可能变化为如下状态:
“蔚来杯“2022牛客暑期多校训练营9 I题: The Great Wall II_第2张图片
“蔚来杯“2022牛客暑期多校训练营9 I题: The Great Wall II_第3张图片
(绿色为更新的部分,紫色为更新后的有效部分)
我们可以对新的 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}} minfk1,x1 来更新新的 min ⁡ f k − 1 , x − 1 \min{f_{k-1,x-1}} minfk1,x1
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}} minfk1,x1
最终答案应为 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(jstackmin(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=jstackmin(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} fk1,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;
}

你可能感兴趣的:(c++,算法)