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;
}
相关推荐
拓恒技术3 小时前
无人机巡检AI算法的工程化落地:从需求到交付的5个核心环节
人工智能·算法·无人机
晨曦中的暮雨3 小时前
限流算法——固定窗口、滑动窗口、漏桶、令牌桶
java·开发语言·算法
怕浪猫4 小时前
Claude Code vs Codex:Computer Use 实现原理深度拆解
算法·面试·github
罗西的思考5 小时前
AI 时代的 “房谋杜断”:如何培养能力、品味与判断力
人工智能·算法·机器学习
淡海水5 小时前
07-02-源码-.NET Core GC分配路径
算法·unity·c#·.netcore·gc·1024程序员节
choumin6 小时前
C++中万能引用与std::forward的作用和用法
c++·万能引用·std forward
下一站春风里7 小时前
位运算完整学习笔记:按位与(&)+ 异或(^)
开发语言·c++·算法
DRobot7 小时前
【机械制图与CAD实战(二十五)】螺纹的表示方法
算法·机械制图
用户老李8 小时前
卡尔曼滤波自学教程 10:无迹卡尔曼滤波(UKF)
算法
深圳市益普科技有限公司9 小时前
半导体良率管理YMS:良率与设备参数的相关性,先拉哪几组数据
人工智能·python·算法