题目描述
我们害怕把这道题题面搞得太无聊了,所以我们决定让这题超短。一个序列被称为是不无聊的,仅当它的每个连续子序列存在一个独一无二的数字,即每个子序列里至少存在一个数字只出现一次。给定一个整数序列,请你判断它是不是不无聊的。
输入格式
第一行一个正整数T,表示有T组数据。每组数据第一行一个正整数n,表示序列的长度,1 <= n <= 200000。接下来一行n个不超过10^9的非负整数,表示这个序列。
输出格式
对于每组数据输出一行,输出"non-boring"表示这个序列不无聊,输出"boring"表示这个序列无聊。
样例输入
4
5
1 2 3 4 5
5
1 1 1 1 1
5
1 2 3 2 1
5
1 1 2 1 1
样例输出
non-boring
boring
non-boring
boring
这道题是今天早上的考题,考试上刚开始根本没有任何思路,后面yy了一个玄学分治,复杂度下界 O ( m n l o g n ) O(mnlogn) O(mnlogn),上界 O ( m n 2 ) O(mn^2) O(mn2)。然后成功的骗了90分,最后一个点完全被卡(显然是出数据的人有意为之),得跑30几秒才能出。
先放一下代码吧。虽然我知道你们没人要看。
#pragma GCC optimize(3)
#include<bits/stdc++.h>
#define MAXN 200005
using namespace std;
inline char nc(){
static char buf[200000],*p1=buf,*p2=buf;
if (p1==p2) {
p2=(p1=buf)+fread(buf,1,200000,stdin);
if (p1==p2) return EOF;
}
return *p1++;
}
int read(){
char c;int x;while(c=nc(),c<'0'||c>'9');x=c-'0';
while(c=nc(),c>='0'&&c<='9') x=x*10+c-'0';return x;
}
int T,n,cnt,flag,f[MAXN],ba[MAXN],sum[MAXN];
struct node{
int a,id;
}a[MAXN];
int cmp(node a,node b){
return a.a<b.a;
}
int dfs(int l,int r){
printf("%d\n",(l+r)>>1);
if(l>=r) return 0;
int p=0,res=0,pp,mid=(l+r)>>1;
for(int i=l;i<=r;i++) sum[ba[i]]=0;
for(int i=l;i<=r;i++) sum[ba[i]]++;
for(int i=l;i<=r;i++){
if(sum[ba[i]]==1){
if(!p||abs(i-mid)<abs(p-mid)) p=i;
}
}
if(!p) return 1;
res|=dfs(l,p-1);
if(res) return 1;
res|=dfs(p+1,r);
return res;
}
int main()
{
T=read();
while(T--){
n=read();cnt=0;
for(int i=1;i<=n;i++)a[i]=(node){read(),i},f[i]=a[i].a;
sort(a+1,a+1+n,cmp);
for(int i=1;i<=n;i++)
if(a[i].a!=a[i-1].a) ba[a[i].id]=++cnt;else ba[a[i].id]=cnt;
puts(dfs(1,n)?"NIE":"TAK");
}
return 0;
}
然后最后一个点怎么卡也卡不过去。于是只能求助题解,一种题解是线段树乱操,好像我也没特别看得懂,也放一下,显然有人能看懂
#include<cstdio>
#include<cstdlib>
#include<algorithm>
#include<cstring>
#define cl(x) memset(x,0,sizeof(x))
using namespace std;
inline char nc()
{
static char buf[100000],*p1=buf,*p2=buf;
if (p1==p2) { p2=(p1=buf)+fread(buf,1,100000,stdin); if (p1==p2) return EOF; }
return *p1++;
}
inline void read(int &x)
{
char c=nc(),b=1;
for (;!(c>='0' && c<='9');c=nc()) if (c=='-') b=-1;
for (x=0;c>='0' && c<='9';x=x*10+c-'0',c=nc()); x*=b;
}
struct SEGTREE{
int T[800005],H[800005];
int M,TH;
inline void Build(int n){
for (M=1,TH=0;M<n+2;M<<=1,TH++);
for (int i=1;i<=2*M;i++) H[i]=0,T[i]=1<<30;
for (int i=M+1;i<=M+n;i++)
T[i]=0;
for (int i=M-1;i;i--)
T[i]=min(T[i<<1],T[i<<1|1]);
}
inline void Pushdown(int rt){
int p;
for (int i=TH;i;i--)
if (H[p=rt>>i])
{
H[p<<1]+=H[p]; H[p<<1|1]+=H[p];
T[p<<1]+=H[p]; T[p<<1|1]+=H[p];
H[p]=0;
}
}
inline void Add(int s,int t,int c){
for (Pushdown(s+=M-1),Pushdown(t+=M+1);s^t^1;)
{
if (~s&1) T[s^1]+=c,H[s^1]+=c;
if ( t&1) T[t^1]+=c,H[t^1]+=c;
T[s>>=1]=min(T[s<<1],T[s<<1|1]);
T[t>>=1]=min(T[t<<1],T[t<<1|1]);
}
while (s>>=1)
T[s]=min(T[s<<1],T[s<<1|1]);
}
inline int Query(int s,int t){
int ret=1<<30;
for (Pushdown(s+=M-1),Pushdown(t+=M+1);s^t^1;s>>=1,t>>=1)
{
if (~s&1) ret=min(ret,T[s^1]);
if ( t&1) ret=min(ret,T[t^1]);
}
return ret;
}
}SEG;
int n,a[200005];
int sx[200005],icnt;
int pre[200005],nxt[200005];
int head[200005];
struct event{
int x,y1,y2;
int f;
bool operator < (const event &B) const{
return x<B.x;
}
}eve[500005];
int tot;
int main()
{
int Q;
freopen("sequence8.in","r",stdin);
freopen("sequence8.out","w",stdout);
read(Q);
while (Q--)
{
read(n); SEG.Build(n); icnt=0;
for (int i=1;i<=n;i++)
read(a[i]),sx[++icnt]=a[i];
sort(sx+1,sx+icnt+1);
icnt=unique(sx+1,sx+icnt+1)-sx-1;
for (int i=1;i<=n;i++)
pre[i]=0,nxt[i]=n+1,a[i]=lower_bound(sx+1,sx+icnt+1,a[i])-sx;
for (int i=1;i<=icnt;i++)
head[i]=0;
for (int i=1;i<=n;i++)
{
pre[i]=head[a[i]];
head[a[i]]=i;
}
for (int i=1;i<=icnt;i++)
head[i]=n+1;
for (int i=n;i>=1;i--)
{
nxt[i]=head[a[i]];
head[a[i]]=i;
}
tot=0;
for (int i=1;i<=n;i++)
{
eve[++tot].x=pre[i]+1,eve[tot].y1=i,eve[tot].y2=nxt[i]-1,eve[tot].f=1;
eve[++tot].x=i+1,eve[tot].y1=i,eve[tot].y2=nxt[i]-1,eve[tot].f=-1;
}
sort(eve+1,eve+tot+1);
try{
for (int i=1,j;i<=tot;i=j+1)
{
j=i;
while (j+1<=tot && eve[j+1].x==eve[j].x)
j++;
for (int t=i;t<=j;t++)
SEG.Add(eve[t].y1,eve[t].y2,eve[t].f);
if (eve[i].x<=n && SEG.Query(eve[i].x,n)==0)
throw(true);
}
printf("TAK\n");
}catch (bool)
{
printf("NIE\n");
}
}
return 0;
}
接下来就讲下今天主要想讲的正解,它的名字叫启发式分治。(其实跟我的代码差别不大,就是它的复杂度是稳定的 O ( n l o g n ) O(nlogn) O(nlogn),这个大家细细观察就能发现差别)所谓启发式也就是挑小的拆,所以枚举拆区间的时候就从两边往中间扫,然后把左右两边较小的区间拆出来,这样就能保证复杂度了。
具体来说,对于每个数x,处理一个pre[x]和nxt[x]表示上一个和下一个出现x的地方,那么对于区间 [ l , r ] [l,r] [l,r],如果 p r e [ x ] < l pre[x]<l pre[x]<l并且 r < n x t [ x ] r<nxt[x] r<nxt[x],那么这个区间只要过x的地方都是安全的,于是就可以下去处理区间 [ l , p x − 1 ] [l,p_x-1] [l,px−1]和 [ p x + 1 , r ] [p_x+1,r] [px+1,r]
废话少说,看代码。
#include<cstdio>
#include<map>
using namespace std;
#define N 200010
int read(){
char c;int x;while(c=getchar(),c<'0'||c>'9');x=c-'0';
while(c=getchar(),c>='0'&&c<='9') x=x*10+c-'0';return x;
}
int n,T,a[N],pre[N],nxt[N];
bool check(int L,int R){
if(L>=R)return 1;
int l=L,r=R;
for(int i=L;i<=R;i++){
if(i&1){if(pre[l]<L&&nxt[l]>R)return(check(L,l-1)&&check(l+1,R));l++;}
else{if(pre[r]<L&&nxt[r]>R)return(check(L,r-1)&&check(r+1,R));r--;}
}
return 0;
}
map<int,int> M;
int main(){
scanf("%d",&T);
while(T--){
scanf("%d",&n);
for(int i=1;i<=n;i++){
scanf("%d",&a[i]);
nxt[M[a[i]]]=i;
pre[i]=M[a[i]];
M[a[i]]=i;
}for(int i=1;i<=n;i++)nxt[M[a[i]]]=n+1;
if(check(1,n))puts("TAK");
else puts("NIE");M.clear();
}return 0;
}