题目大意
「NOIP2021模拟赛8.18 C」另一个序列(aseq)
定义一个序列的所有连续子序列中都要至少存在一个数在这个子序列中只出现过一次,称这个序列是合理的,判断给出的序列是否合理
问题求解
考试的时候一直在思考如何用树状数组+扫描线来进行连续子序列是否合法的判断,但是发现无论怎么样至少要枚举所有子序列才能求解,逃不掉\(O(n^2)\)
所以我们要换种思考问题的方式,很容易可以求出\(pre[x],nxt[x]\)分别表示前面第一个一样的和后面第一个一样的
然后在\((L,R)\)内存在一个点\(i\)满足\(pre[x]<L\&\&nxt[x]\),显然在\((L,R)\)的子区间内,过\(i\)的区间肯定是合理的,所以要考虑不过\(i\)的区间,如果不存在这样的点,显然就不是合理的了
就可以分别\(check(L,x-1)\&\&check(x+1,R)\)由于要保证时间复杂度,自然而然的就想到了有点类似启发式合并一样的想法,从中间开始找就可以保证是\(log\)级别的了
代码实现
#include<bits/stdc++.h>
using namespace std;
const int maxn=200005;
int T,N,a[maxn],b[maxn],tot,pre[maxn],nxt[maxn],vis[maxn];
inline int read(){
int ret=0,f=1;char ch=getchar();
while(ch>'9'||ch<'0'){if(ch=='-')f=-f;ch=getchar();}
while(ch<='9'&&ch>='0')ret=ret*10+ch-'0',ch=getchar();
return ret*f;
}
bool check(int L,int R){
if(L>=R)return 1;
int mid=(R-L>>1)+L;
for(int i=0;mid-i>=L&&mid+i<=R;i++){
if(pre[mid+i]<L&&nxt[mid+i]>R)return check(L,mid+i-1)&check(mid+i+1,R);
if(pre[mid-i]<L&&nxt[mid-i]>R)return check(L,mid-i-1)&check(mid-i+1,R);
}
return 0;
}
int main(){
freopen("aseq.in","r",stdin);
freopen("aseq.out","w",stdout);
T=read();
while(T--){
N=read();
for(int i=1;i<=N;i++)a[i]=b[i]=read();
tot=unique(b+1,b+1+N)-b-1;
sort(b+1,b+1+tot);
for(int i=1;i<=N;i++)a[i]=lower_bound(b+1,b+1+tot,a[i])-b;
memset(vis,0,sizeof vis);
for(int i=1;i<=N;i++){
if(vis[a[i]])pre[i]=vis[a[i]];
else pre[i]=0;vis[a[i]]=i;
}
memset(vis,0,sizeof vis);
for(int i=N;i;i--){
if(vis[a[i]])nxt[i]=vis[a[i]];
else nxt[i]=N+1;vis[a[i]]=i;
}
printf("%s\n",check(1,N)?"YES":"NO");
}
return 0;
}