BZOJ4059Non-boring sequences——启发式分治

题目描述

我们害怕把这道题题面搞得太无聊了,所以我们决定让这题超短。一个序列被称为是不无聊的,仅当它的每个连续子序列存在一个独一无二的数字,即每个子序列里至少存在一个数字只出现一次。给定一个整数序列,请你判断它是不是不无聊的。

输入格式

第一行一个正整数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 ] &lt; l pre[x]&lt;l pre[x]<l并且 r &lt; n x t [ x ] r&lt;nxt[x] r<nxt[x],那么这个区间只要过x的地方都是安全的,于是就可以下去处理区间 [ l , p x − 1 ] [l,p_x-1] [l,px1] [ 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;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值