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;
}