uva 1608 non-boring sequences不无聊的序列

题目

Description

We were afraid of making this problem statement too boring, so we decided to keep it short. A sequence
is called non-boring if its every connected subsequence contains a unique element, i.e. an element such
that no other element of that subsequence has the same value.
Given a sequence of integers, decide whether it is non-boring.

Input

The first line of the input contains the number of test cases T. The descriptions of the test cases follow:
Each test case starts with an integer n (1 ≤ n ≤ 200000) denoting the length of the sequence. In
the next line the n elements of the sequence follow, separated with single spaces. The elements are
non-negative integers less than 109
.

Output

Print the answers to the test cases in the order in which they appear in the input. For each test case
print a single line containing the word ‘non-boring’ or ‘boring’.

Sample Input

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

Sample Output

non-boring
boring
non-boring
boring

============================
看了很多人的博客,讲这个题的解法,有的讲的很简单,研究了半天,大概清楚了思路,先记下来,免得忘了。

题意:

给出一个序列,如果他的任何一个连续的子序列都有至少一个特殊元素(在这个子序列中只出现一次),则这个序列是non-boring,否则是boring

解题思路:

1、首先看看给定的整个序列中有没有特殊元素(在这个序列中只出现一次),如果没有任何一个特殊元素,那这个序列就是boring的了
2、如果这个序列中出现了一个特殊元素(假设他的下标是第i个元素),那么接下来要看看这个序列的所有连续子序列是不是也都至少有一个特殊元素,可以分以下几种情况考虑:
1.1 所有包含有这个特殊元素的连续子序列都是non-boring
1.2 除了1.1的子序列以外(即除了这个第i个特殊元素以外),只需要判断一下[1,i-1] 和[i+1,n]这两个子序列是不是non-boring,这就是分治的思想,可以通过递归调用实现。

接下来考虑:怎么判断一个序列中有没有特殊元素(在这个序列中只出现了一次的元素)呢?假设这个序列的每一个元素存放在数组a[]中,同时使用数组l[]和数组r[]记录每一个元素a[i]在这个序列中左边最近出现的下标位置 和 右边最近出现的下标位置。对于每一个元素a[i],
例如:序列 89798,数组a[]如下:a[ ] = {8,9,7,9,8}
l[ ] = { -1, -1, -1, 1, 0} , r[ ] = { 4, 3, 5, 5, 5}

再如序列 88988,a[ ] = {8, 8, 9 ,8, 8}, l[ ] = {-1, 0, -1, 1, 3}, r[ ] = {1, 3, 5, 4, 5}
序列 56789:a[ ] = {5, 6, 7, 8, 9}, l[ ] = {-1, -1, -1, -1, -1}, r[ ] = {5, 5, 5, 5, 5}

在一个区间为[left,right]的序列内,对于其中的某一个元素a[i],它在左边最近出现的下标是l[i](如果在它左边没有重复出现该元素,则l[i]为-1),它在右边最近出现的下标是r[i](如果在它右边没有重复出现该元素,则r[i]为n),
当 l[i] < left 并且 r[i] >right 时,说明a[i]在区间[left, right]中没有重复出现,即区间[left, right] 的序列是 non-boring,这时,只需要递归调用去判断除去a[i]元素后的两个子序列:[left, i-1] 和 [i+1, right]即可。

当 “ l[i] < left 并且 r[i] >right ”不成立时,说明a[i]在左边或右边有相同的元素出现,则序列[left, right]不是non-boring。

下一个问题:如何生成序列a[ ]的两个数组l[ ]和r[ ]?在这里借助一个map,map可以当做数组用,而且可以直接调用map的count()、clear()等,比较方便。用 map <int, int> m,定义一个map的m,m中存放a[ ]中每个元素在序列中出现的下标位置。
生成l[ ]数组时,对a[ ]中的每一个元素a[i],如果m中记录该元素下标的个数为0,(m.count(a[i]) 为0),说明a[i]以前在序列的左边还没出现过,因此在 l[i]记为“-1”,否则,说明a[i]以前在序列的左边出现过,就在l[i]中记录上次出现的位置,即:l[i]=m[a[i]];最后无论哪种情况,都把本次a[i]在序列中出现的位置i记录在m中(m[a[i]]=i)。
生成 r[ ]数组也是类似,只不过用n代替“-1”

最后,当有了序列数组a[ ],并生成了l[ ]和r[ ]后,就递归调用judge()。
在judge()中,循环依次判断每一个a[i],看看a[i]在本次区间[left, right]中是否是特殊元素,如果是,就再以区间[left, i-1] 和[i+1,right]递归调用judge()。代码如下:

    bool judge(int left,int right)
    {
        if(left>=right)
            return true;
        for(int i=left;i<=right;i++)
        {
            if(l[i]<left&&r[i]>right)
                return judge(left,i-1)&&judge(i+1,right);
        }
        return false;
    }

代码没有问题,但会超时,看了大神的题解之后才知道原来可以 从两端同时遍历 i(而不是从单一的从左往右) ,这样就可以每次把子问题缩小一半的复杂度,从而优化时间。

代码


    #include <iostream>
    #include <algorithm>
    #include <map>
    #include <cstdio>
    using namespace std;
    
    const int maxn= 200000+5;
    int a[maxn];
    int l[maxn],r[maxn];
    
    map<int,int> m;
    int n;
    
    bool judge(int left,int right)
    {
        if(left>=right)
            return true;
        for(int i=0;i<=(right-left)/2;i++)
        {
            if(l[left+i]<left && r[left+i]>right)
                return judge(left,left+i-1)&&judge(left+i+1,right);
            if(l[right-i]<left && r[right-i]>right)
                return judge(left,right-i-1)&&judge(right-i+1,right);
        }
        return false;
    }
    
    
    int main()
    {
        int T;
        scanf("%d",&T);
        while(T--)
        {
            scanf("%d",&n);
            for(int i=0;i<n;i++)
                scanf("%d",a+i);
            //记录第i个元素左边第一个相同元素的位置
            m.clear();
            for(int i=0;i<n;i++)
            {
                if(m.count(a[i]))
                    l[i]=m[a[i]];
                else
                    l[i]=-1;
                m[a[i]]=i;
            }
            //记录第i个元素右边第一个相同元素的位置
            m.clear();
            for(int i=n-1;i>=0;i--)
            {
                if(m.count(a[i]))
                    r[i]=m[a[i]];
                else
                    r[i]=n;
    
                m[a[i]]=i;
            }
            if(judge(0,n-1))
                cout<<"non-boring"<<endl;
            else
                cout<<"boring"<<endl;
        }
        return 0;
    }


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值