对于区间 [l,r][l,r] ,如果 aiai 在区间内只出现了一次,那么跨过 aiai 的区间一定是合法的,只需要判断 [l,i−1][l,i−1] 和 [i+1,r][i+1,r] 就好了。
这样就得到了一个分治的做法:每次枚举区间中一个点,如果在区间中只出现过一次就分治下去。
有一个优化:把枚举顺序变成每次左边枚举一个点、右边枚举一个点。
然后算下复杂度:
T(n)=max{T(k)+T(n−k)+min(k,n−k)}T(n)=max{T(k)+T(n−k)+min(k,n−k)}
可以看成启发式合并的逆过程,复杂度为 O(nlogn)O(nlogn) 。
#include<bits/stdc++.h>
using namespace std;
inline char nc(){
static char buf[100000],*p1=buf,*p2=buf;
return p1==p2&&(p2=(p1=buf)+fread(buf,1,100000,stdin),p1==p2)?EOF:*p1++;
}
inline void Read(int& x){
char c=nc();
for(;c<'0'||c>'9';c=nc());
for(x=0;c>='0'&&c<='9';x=(x<<3)+(x<<1)+c-48,c=nc());
}
const int N=200010;
int k,n,m,T;
int a[N],lst[N],nxt[N];
int c[N<<2];
map<int,int>M;
bool Check(int l,int r){
if(l>=r)return 1;
int p1=l,p2=r;
bool b=0;
while(p1<=p2){
if(!b){
if(lst[p1]<l&&nxt[p1]>r)return Check(l,p1-1)&&Check(p1+1,r);
p1++;
}else{
if(lst[p2]<l&&nxt[p2]>r)return Check(l,p2-1)&&Check(p2+1,r);
p2--;
}
b^=1;
}
return 0;
}
int main(){
Read(T);
while(T--){
Read(n);M.clear();
for(int i=1;i<=n;i++){
Read(a[i]);
lst[i]=M[a[i]];M[a[i]]=i;
}
M.clear();
for(int i=n;i;i--){
nxt[i]=M[a[i]];M[a[i]]=i;
if(!nxt[i])nxt[i]=n+1;
}
puts(Check(1,n)?"non-boring":"boring");
}
}

本文介绍了一种基于分治思想的算法实现,通过枚举区间中只出现一次的元素进行递归分解,最终判断整个区间的合法性。算法采用了启发式合并的逆过程优化,实现了O(nlogn)的时间复杂度。

被折叠的 条评论
为什么被折叠?



