Non-boring sequences
题意:
给出一个序列,如果他的任何一个连续的子序列都有至少一个特殊元素(在这个子序列中只出现一次),则这个序列是non-boring,否则是boring
解题思路:
首先要解决的问题是如何快速判断一个子序列是否有特殊元素,这里采用的办法是使用map记录下标为 i 的元素左边最近的相同元素的下标(设为l[i]),和右边最近的相同元素的下标(设为r[i])。
假设子序列的区间是[left,right],只要遍历其中的元素,看是否存在 l[i] < left 并且 r[i] > right 的,如果有就说明这个子序列是中存在特殊元素,就是第 i 个。
由这个特殊元素,我们可以得出[left,i+1],[left,i+2]……和[i-1,right],[i-2,right]……等区间的子序列都包含这个特殊元素。
由此,我们只需要证明[left,i-1]和[i+1,right]这两个区间也包含特殊元素,就可以推出[left,right]这个区间是non-boring了。
按照这个思路写出递归函数:
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(而不是从单一的从左往右) ,这样就可以每次把子问题缩小一半的复杂度,从而优化时间。
Code:
#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;
}