Codeforces Round /#686 (Div. 3) F. Array Partition(ST,二分)


题目地址

https://codeforces.com/contest/1454/problem/F

题目大意

给一串序列,是否能有一种方案把序列划分成连续的三段$d1,d2,d3$,使得$max(d1)=min(d2)=max(d3)$。

比如序列$[1,2,3,3,3,4,4,3,4,2,1]$可以划分成$[1,2,3,3,3,4],[4],[3,4,2,1]$

解法

这题我是根本想不到要二分。这题的二分比较奇妙。

首先预处理两个$max$,从头开始的$max$值从结尾开始的$max$值(用于快速查询$d1$和$d3$)。再维护一个st表查询区间最小值$min$(用于快速查询$d2$)

设$d2$左右端点分别为$l,r$。

我们枚举$l$再二分寻找$r$,复杂度$O(n^2)$

  • 如果$min(d2)>max(d1)$,说明d2太长或者d1太短,这时候l已经固定,所以只能让d2变短,即mid向左移动。

  • 如果$max(d1)>max(d3)$,说明d1太长或者d3太短,这时候l已经固定,所以只能让d3变长,即$mid$向左移动。

再看看这句对不对?

  • 如果$min(d2)>max(d3)$,说明$d2$太长或者$d3$太短,所以应该向左移动$mid$?

不对,因为在向左移动$mid$的时候,$max(d3)$增大的同时$min(d2)$也在增大,并不是在向更接近的地方行进,所以不行!

while(l<r){
	mid = (l+r)>>1;
	tmp = qry(i,mid);
	//这里我一开始多写了个条件就是max2和tmp比较的,这个不能有
	//因为扩的时候max2和tmp都可能下降,并不是往更接近的方向走
    if(tmp>max1[i-1]||max1[i-1]<max2[mid+1])
		l = mid+1;
	else if(tmp<max1[i-1]||max1[i-1]>max2[mid+1])
		r = mid-1;
	else break;
}

代码

#include<iostream>
#include<algorithm>
#include<cstring>
#include<cstdio>
#include<vector>
#include<queue>
#include<cmath>
using namespace std;
#define rep(i,a,b) for(int i=(a);i<=(b);i++)
#define repb(i,a,b) for(int i=(a);i>=(b);i--)
#define ll long long
#define log(x) (31-__builtin_clz(x))//谢谢hjt
const int MAXN = 2e5+5;
int cansel_sync=(ios::sync_with_stdio(0),cin.tie(0),0);
const int LOGN = log(MAXN)/log(2); 
int M[MAXN][LOGN+3]; 
int a[MAXN];
int z,m,n;
int max1[MAXN];
int max2[MAXN];

void init(){//初始化,复杂度O(nlogn) 
	for(int i=1;i<=n;i++) M[i][0]=i;//长度为1的区间最值是自己 
	for(int j=1;j<=LOGN;j++){
		for(int i=1;i<=n-(1<<j)+1;i++){
			if(a[M[i][j-1]]<a[M[i+(1<<(j-1))][j-1]]) M[i][j] = M[i][j-1];//这里以最小值为例 
			else M[i][j] = M[i+(1<<j-1)][j-1];
		}
	} 
}
inline int query(int l,int r){
	int k = log(r-l+1)/log(2);//向下取整
	if(a[M[l][k]]<a[M[r-(1<<k)+1][k]]) return M[l][k];
	else return M[r-(1<<k)+1][k];
}
inline int qry(int l,int r){
    return a[query(l,r)];//直接返回数值
}
bool flag;
void solve(){
    int la,lb,lc;
    flag = 0;
    cin>>n;
    rep(i,1,n) cin>>a[i];
    init();
    max1[0]=max2[n+1] = 0;
    rep(i,1,n) max1[i] = max(max1[i-1],a[i]);//前缀最大值
    repb(i,n,1) max2[i] = max(max2[i+1],a[i]);
    int l,r,mid,tmp;
    rep(i,2,n-1){//枚举中间段的开头
        l = i,r = n-1;//二分
        while(l<r){
            mid = (l+r)>>1;
            tmp = qry(i,mid);
            //这里我一开始多写了个条件就是max2和tmp比较的,这个不能有
            //因为扩的时候max2和tmp都可能下降,并不是往更接近的方向走
            if(tmp>max1[i-1]||max1[i-1]<max2[mid+1])
                l = mid+1;
            else if(tmp<max1[i-1]||max1[i-1]>max2[mid+1])
                r = mid-1;
            else break;
        }
        mid = (l+r)>>1;
        if(mid>=i&&mid<=n-1){
            tmp = qry(i,mid);
            if(tmp==max1[i-1]&&tmp==max2[mid+1]){
                flag = 1;
                la = i-1,lb = mid-i+1,lc = n-mid;
                break;
            }
        }
    }
    if(!flag) cout<<"NO"<<endl;
    else{
        cout<<"YES"<<endl;
        cout<<la<<' '<<lb<<' '<<lc<<endl;
    }
}
signed main(){
    int z;
    cin>>z;
    while(z--) solve();
}
//二分yyds

文章作者: REXWind
版权声明: 本博客所有文章除特別声明外,均采用 CC BY 4.0 许可协议。转载请注明来源 REXWind !
评论
  目录