[BZOJ4059][Cerc2012]Non-boring sequences(扫描线+线段树||暴力分治)

70 篇文章 0 订阅
13 篇文章 0 订阅

题目描述

传送门

题目大意:被称为是不无聊的,仅当它的每个连续子序列存在一个独一无二的数字,即每个子序列里至少存在一个数字只出现一次。给定一个整数序列,请你判断它是不是不无聊的。

题解

首先一个扫描线(树套树)的做法
对于每一个点i,假设前面和后面和它相同的数字的位置为pre和nxt,那么左端点在[pre+1,i],右端点在[i,nxt-1]中的子区间都是合法的。将[pre+1,i]看成横坐标,[i,nxt-1]看成纵坐标,可以在坐标系中抽象出来一个矩形,然后用扫描线判断一下这n个矩形是否能将一个上三角覆盖就行了
但是被卡得死T活T。。线段树常数确实大
然后就学习了一下一个网上的分治做法
大概就是判断一个区间[l,r]是否是合法的,只需要在[l,r]中找到一个唯一出现的点p,然后判断[l,p-1]和[p+1,r]是否是合法的就行了,这就相当于是一个分治;但是如何找到p呢?对于某一个点判断是 O(1) 的,但是枚举的时候从两边向中间找,这样时间复杂度就是 O(nlogn) 的了
好像是唐教给出的证明吧?网上还有一个我认为很不错的直观证明:http://whx991201.is-programmer.com/posts/190036.html感觉很有趣

代码

扫描线tle

#include<algorithm>
#include<iostream>
#include<cstring>
#include<cstdio>
#include<cmath>
using namespace std;
#define N 200005

int T,n,LSH,cnt;
int a[N],b[N],lsh[N],pre[N],nxt[N],head[N],tail[N];
struct data{int x,l,r,val;}line[N<<1];
int sum[N*4],cover[N*4];
bool flag;

void clear()
{
    n=LSH=cnt=0;
    memset(sum,0,sizeof(sum));memset(cover,0,sizeof(cover));
}
int cmp(data a,data b) {return a.x<b.x;}
void update(int now,int l,int r)
{
    if (cover[now]) sum[now]=r-l+1;
    else if (l==r) sum[now]=0;
    else sum[now]=sum[now<<1]+sum[now<<1|1];
}
void change(int now,int l,int r,int lr,int rr,int v)
{
    if (lr>rr) return;
    int mid=(l+r)>>1;
    if (lr<=l&&r<=rr)
    {
        cover[now]+=v;update(now,l,r);
        return;
    }
    if (lr<=mid) change(now<<1,l,mid,lr,rr,v);
    if (mid+1<=rr) change(now<<1|1,mid+1,r,lr,rr,v);
    update(now,l,r);
}
int cmplsh(int x,int y) {return b[x]<b[y];}
int main()
{
    scanf("%d",&T);
    while (T--)
    {
        clear();
        scanf("%d",&n);b[0]=-1;
        for (int i=1;i<=n;++i)
        {
            scanf("%d",&b[i]),lsh[i]=i;
            pre[i]=nxt[i]=head[i]=tail[i]=0;
        }
        sort(lsh+1,lsh+n+1,cmplsh);
        for (int i=1;i<=n;++i)
            if (b[lsh[i]]!=b[lsh[i-1]]) a[lsh[i]]=++LSH;
            else a[lsh[i]]=LSH;
        for (int i=1;i<=n;++i)
        {
            pre[i]=tail[a[i]];
            tail[a[i]]=i;
        }
        for (int i=n;i>=1;--i)
        {
            nxt[i]=head[a[i]];
            head[a[i]]=i;
        }
        for (int i=1;i<=n;++i)
        {
            int l=pre[i]+1,r=(nxt[i])?nxt[i]-1:n;
            line[++cnt].x=l-1,line[cnt].l=i-1,line[cnt].r=r,line[cnt].val=1;
            line[++cnt].x=i,line[cnt].l=i-1,line[cnt].r=r,line[cnt].val=-1;
        }
        sort(line+1,line+cnt+1,cmp);
        bool flag=1;
        for (int i=1,j;i<=cnt;i=j)
        {
            j=i;
            if (i>1&&sum[1]!=n-line[i].x+1){flag=0;break;}
            while (j<=cnt&&line[j].x==line[i].x)
            {
                change(1,1,n,line[j].l+1,line[j].r,line[j].val);
                ++j;
            }
        }
        if (flag) puts("non-boring");
        else puts("boring");
    }
}

暴力分治

#include<algorithm>
#include<iostream>
#include<cstring>
#include<cstdio>
#include<cmath>
using namespace std;
#define N 200005

int T,n,LSH;
int a[N],b[N],lsh[N],pre[N],nxt[N],head[N],tail[N];

bool check(int l,int r)
{
    if (l>=r) return 1;
    int t[2]={l,r};
    for (int p=0;t[0]<=t[1];(p^=1)?t[0]++:t[1]--)
        if (pre[t[p]]<l&&nxt[t[p]]>r)
            return check(l,t[p]-1)&&check(t[p]+1,r);
    return 0;
}
int cmplsh(int x,int y){return b[x]<b[y];}
int main()
{
    scanf("%d",&T);
    while (T--)
    {
        scanf("%d",&n);b[0]=-1;
        for (int i=1;i<=n;++i)
        {
            scanf("%d",&b[i]),lsh[i]=i;
            pre[i]=tail[i]=0;nxt[i]=head[i]=n+1;
        }
        sort(lsh+1,lsh+n+1,cmplsh);LSH=0;
        for (int i=1;i<=n;++i)
            if (b[lsh[i]]!=b[lsh[i-1]]) a[lsh[i]]=++LSH;
            else a[lsh[i]]=LSH;
        for (int i=1;i<=n;++i)
        {
            pre[i]=tail[a[i]];
            tail[a[i]]=i;
        }
        for (int i=n;i>=1;--i)
        {
            nxt[i]=head[a[i]];
            head[a[i]]=i;
        }
        bool flag=check(1,n);
        if (flag) puts("non-boring");
        else puts("boring");
    }
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值