AT_arc180_d [ARC180D] Division into 3 题解

AT_arc180_d ARC180D Division into 3 题解

思路

先考虑拆分为两个连续子序列的情况,设 n n n 为序列长度, L , M , R L,M,R L,M,R 表示当前序列最左边,最右边和除了左右两个以外的中间的最大值。当 L L L 为最大值时,无论选择哪个前缀,前缀最大值一定是 L L L,那么就要让选择的后缀最小,由于后缀一定包括 R R R,所以分割为 1 , n − 1 , n , n 1,n-1,n,n 1,n−1,n,n 时最优的, R R R 同理, 1 , 1 , 2 , n 1,1,2,n 1,1,2,n 是最优的。当 M M M 最大时,当把他划分为前缀时,前缀的最大值一定是 M M M,则后缀的最大值最小为 R R R,所以与 L L L 或 R R R 最大时同理,所以对于划分为两各连续子序列的情况,最优划分情况一定为 1 , 1 , 2 , n 1,1,2,n 1,1,2,n 或 1 , n − 1 , n , n 1,n-1,n,n 1,n−1,n,n。

再考虑拆分为三个连续子序列的情况,设 M X MX MX 表示最大值, L , R L,R L,R 表示序列的最左侧,最右侧元素。

当 M X MX MX 被划分为中间时,中间的部分最大值一定为 M X MX MX,所以只要求前后缀最大值最小,答案即为 L + R + M X L+R+MX L+R+MX。

当 M X MX MX 被划分为左侧时,我们要选择一个不包含 M X MX MX 的后缀使得这个后缀划分为两个连续子序列时答案最小。

设不包含 M X MX MX 的最大后缀的区间为 l , r l,r l,r,对于处理后缀划分为 l , r − 1 , r , r l,r-1,r,r l,r−1,r,r 的情况,由于是查询区间的后缀, a r a_r ar 是固定的,所以只需要让 1 , r − 1 1,r-1 1,r−1 的最大值最小,又由于这个区间一定包含 a r − 1 a_{r-1} ar−1,最大值一定不小于 a r − 1 a_{r-1} ar−1,所以一定会取 a r − 1 + a r a_{r-1}+a_r ar−1+ar,而这种情况又刚好符合划分为 l , l , l + 1 , r l,l,l+1,r l,l,l+1,r 的情况,所以可以直接归到 l , l , l + 1 , r l,l,l+1,r l,l,l+1,r 的处理。

现在就只需要处理选择一个后缀,使得将其划分为 l , l , l + 1 , r l,l,l+1,r l,l,l+1,r 的答案最小。由于 r r r 是固定的,考虑对于每一个 r r r 求出这个答案。考虑拆贡献,拆为 l , l l,l l,l 和 l + 1 , r l+1,r l+1,r 的贡献,对于 l + 1 , r l+1,r l+1,r,我们要求出这个区间内的最大值,并且要算加上 a l a_l al 的贡献,这就让我们想到了一个比较经典的 trick,用单调栈加线段树维护每个位置到当前为止的最大值并且支持算上 a l a_l al 的贡献(即用单调栈维护最大值,当当前位置最大值大于栈顶元素时,不断弹出单调栈栈顶的元素,将那个元素影响的区间的最大值改为当前位置的值(即加上差值),不了解的可以去做一下CF526F Pudding Monsters)。

那么接下来的解法就呼之欲出了,算出每个询问要查询的后缀,然后离线处理,从 1 到 n n n,用线段树加单调栈维护每个位置到当前位置的答案,然后对于处于当前位置的查询,在线段树上查一下最小值即可。

对于 M X MX MX 被划分到右侧的情况同理,算出每个询问要查询的前缀,然后从 n n n 到 1 用线段树加单调栈处理即可。

代码使用了st 表和线段树,时间复杂度 O ( n log ⁡ n ) O(n \log n) O(nlogn)。

代码

cpp 复制代码
#include<bits/stdc++.h>
#define lc(p) ((p)<<1)
#define rc(p) ((p)<<1|1) 
using namespace std;
typedef long long ll;
int n,q,a[250010],stl[250010][21],str[250010][21],lg2[250010];
int getl(int l,int r){//找最靠左的最大值的位置 
	int d=lg2[r-l+1];
	if(a[stl[l][d]]>=a[stl[r-(1<<d)+1][d]])return stl[l][d];
	return stl[r-(1<<d)+1][d];
} 
int getr(int l,int r){//找最靠右最大值的位置 
	int d=lg2[r-l+1];
	if(a[str[l][d]]>a[str[r-(1<<d)+1][d]])return str[l][d];
	return str[r-(1<<d)+1][d];
}
int ans[250010];//记录答案 
struct N{
	int x,id,v;
};
vector<N> ql[250010],qr[250010];//ql:记录查询的后缀,qr:记录查询的前缀 
struct T{//维护区间最小值的线段树 
	int mn,la;
}tr[1000010];
void pushup(int p){
	tr[p].mn=min(tr[lc(p)].mn,tr[rc(p)].mn);
}
void pushdown(int p){
	if(tr[p].la){
		tr[lc(p)].la+=tr[p].la;
		tr[lc(p)].mn+=tr[p].la;
		tr[rc(p)].la+=tr[p].la;
		tr[rc(p)].mn+=tr[p].la;
		tr[p].la=0;
	} 
}
void bt(int p,int l,int r){
	tr[p]={1000000000,0};
	if(l==r)return ;
	int mid=(l+r)>>1;
	bt(lc(p),l,mid);
	bt(rc(p),mid+1,r); 
}
void change(int p,int l,int r,int x,int v){//单点改 
	if(l==r){
		tr[p].mn=v;
		return ;
	}
	pushdown(p);
	int mid=(l+r)>>1;
	if(x<=mid)change(lc(p),l,mid,x,v);
	else change(rc(p),mid+1,r,x,v);
	pushup(p);
}
void changed(int p,int l,int r,int x,int y,int v){//区间加 
	if(l>=x&&r<=y){
		tr[p].mn+=v;
		tr[p].la+=v;
		return ;
	}
	pushdown(p);
	int mid=(l+r)>>1;
	if(x<=mid)changed(lc(p),l,mid,x,y,v);
	if(y>mid)changed(rc(p),mid+1,r,x,y,v);
	pushup(p);
}
int find(int p,int l,int r,int x,int y){//查询区间最小值 
	if(l>=x&&r<=y)return tr[p].mn;
	pushdown(p);
	int mid=(l+r)>>1;
	if(y<=mid)return find(lc(p),l,mid,x,y);
	if(x>mid)return find(rc(p),mid+1,r,x,y);
	return min(find(lc(p),l,mid,x,y),find(rc(p),mid+1,r,x,y)); 
}
int main(){
	ios::sync_with_stdio(0);
	cin.tie(0);
	cin>>n>>q;
	for(int i=2;i<=n;i++)lg2[i]=lg2[i>>1]+1;
	for(int i=1;i<=n;i++){
		cin>>a[i];
		stl[i][0]=str[i][0]=i;
	}
	for(int i=1;i<=20;i++){//st表预处理 
		for(int x=1;x+(1<<i)-1<=n;x++){
			if(a[stl[x][i-1]]>=a[stl[x+(1<<i-1)][i-1]])stl[x][i]=stl[x][i-1];
			else stl[x][i]=stl[x+(1<<i-1)][i-1]; 
			if(a[str[x][i-1]]>a[str[x+(1<<i-1)][i-1]])str[x][i]=str[x][i-1];
			else str[x][i]=str[x+(1<<i-1)][i-1]; 
		}
	}
	for(int i=1;i<=q;i++){
		int l,r;
		cin>>l>>r;
		int mi=getl(l+1,r-1);
		ans[i]=a[l]+a[mi]+a[r];//MX被划分在中间时 
		mi=getl(l,r-2);
		ql[r].push_back({mi+1,i,a[mi]});//MX被划分在左侧时 
		mi=getr(l+2,r);
		qr[l].push_back({mi-1,i,a[mi]});//MX被划分在右侧时
	}
	bt(1,1,n);
	stack<int> stk;
	for(int i=1;i<=n;i++){
		change(1,1,n,i,0);//将当前位置的值置为0 
		int la=i,lax=0;//上一个最大值的位置和修改钱的最大值 
		while(!stk.empty()&&a[i]>a[stk.top()]){//单调栈 
			changed(1,1,n,stk.top()+1,la,a[i]-lax);//加上当前值与最大值的差值 
			la=stk.top();
			lax=a[la];
			stk.pop();
		}
		if(!stk.empty())changed(1,1,n,stk.top()+1,la,a[i]-lax);
		else changed(1,1,n,1,la,a[i]-lax);
		stk.push(i);
		if(i>1)changed(1,1,n,i,i,a[i-1]);//加上a[l] 
		for(N j:ql[i]){//处理询问 
			ans[j.id]=min(ans[j.id],j.v+find(1,1,n,j.x+1,i));
		}
	}
	bt(1,1,n);
	while(!stk.empty())stk.pop();
	for(int i=n;i;i--){
		change(1,1,n,i,0);
		int la=i,lax=0;
		while(!stk.empty()&&a[i]>a[stk.top()]){
			changed(1,1,n,la,stk.top()-1,a[i]-lax);
			la=stk.top();
			lax=a[la];
			stk.pop();
		}
		if(!stk.empty())changed(1,1,n,la,stk.top()-1,a[i]-lax);
		else changed(1,1,n,la,n,a[i]-lax);
		stk.push(i);
		if(i<n)changed(1,1,n,i,i,a[i+1]);//加上a[r] 
		for(N j:qr[i]){
			ans[j.id]=min(ans[j.id],j.v+find(1,1,n,i,j.x-1));
		}
	}
	for(int i=1;i<=q;i++){
		cout<<ans[i]<<'\n'; 
	}
	return 0;
}
相关推荐
殷色玫瑰1 小时前
C++ STL:stack、queue、priority_queue 与容器适配器详解
java·开发语言·数据结构·c++·算法·visualstudio
努力努力再努力wz2 小时前
【CUDA入门系列】CUDA编程实战:设备选择、显存管理、Kernel、Stream 与 Event 全流程梳理
开发语言·c++
Evand J2 小时前
【电机滤波例程1】PMSM永磁同步电机转速与位置估计,MATLAB程序:标准无迹卡尔曼滤波、模型说明与仿真验证
算法·matlab·电机·ekf·负载·卡尔曼滤波
沫璃染墨2 小时前
《从零入门Linux系统篇(五十八):线程篇·十一——线程安全与死锁详解:从可重入到多锁管理》
linux·服务器·开发语言·c++·驱动开发·安全·架构
打码的老程是远篁2 小时前
手把手教你玩转大模型——3. Transformer 的位置编码——模型怎么知道谁在前,谁在后?
人工智能·算法
嘎嘎风2 小时前
MiniMind 学习笔记之 06 一张图、一百行:MiniMind 的骨架
算法·源码
郝学胜_神的一滴2 小时前
C++ Templates 05:那些容易踩坑的技巧性基础知识
c++·visual studio
用户7341681035482 小时前
只盯被动响应做状态监测?论“控制信号”在变工况故障诊断中的重要作用
算法
Ivanqhz2 小时前
MQA、GQA、稀疏/滑动窗口注意力及混合范式
人工智能·算法·机器学习