题目链接:点击查看
题目大意:k-bag 序列的定义是由多个 1 ~ k 的排列顺序连接起来的序列,现在问给定的序列是不是 k-bag 的连续子串
题目分析:读完题目后,如果给定的序列是 k-bag 的连续子串的话,那么可以将所有情况都分为以下三种类型:
- 一个部分k-bag的前缀 + 数个(至少一个)完整的k-bag + 一个部分k-bag的后缀
- 一个部分k-bag的前缀 + 一个部分k-bag的后缀
- 一个部分k-bag
判断部分k-bag和完整的k-bag需要用到两种不同的方法,判断部分k-bag的话,只需要判断区间内的每个数都是否不同即可,即区间的长度等于区间内不同的数的个数,这个利用 dp 和 unordered_map 搭配就能 O( n ) 的时间内预处理出来了,这里用 cnt1[ i ] 表示 1 ~ i 内有多少个不同的数(前缀),cnt2[ i ] 表示 i ~ n 内有多少个不同的数(后缀)
接下来需要判断完整的k-bag,如果也是用 “ 区间内有多少个不同的数 ” 去判断的话,需要用到主席树,但可能会超时(群友是这样说的),而且代码复杂度也大大上升,很容易写崩,这里用到一种哈希的方法,不会证明只会用,大概就是一个连续的序列 [ l , r ] ,维护 sum 和 xor ,sum = l + ( l + 1 ) + ... + ( r - 1 ) + r ,xor = l ^ ( l + 1 ) ^ ... ^ ( r - 1 ) ^ r ,形成一个二元对 < sum , xor > ,这个二元对和 [ l , r ] 是一一对应的,可以称之为 RSB哈希 ( rsb学长自己起的名字 )
上面开个小玩笑,回到这个题目中来,我们可以再次 O( n ) 维护一下整个数列的 sum前缀和 和 xor前缀异或和,然后再小分一下类,当 k < n 时,只会有上面的情况 1 和情况 2 ,这样我们可以枚举 [ 1 , k ] 作为滑动窗口的起点,然后检查每个长度为 k 的滑动窗口,对于每个长度为 k 的滑动窗口,我们现在可以 O( 1 ) 求出当前窗口的xor之和以及sum之和,判断一下是否等于 1 ~ k 的 xor 和 sum 就好了,当 k >= n 时,就只有情况 2 和情况 3 了,此时我们可以当做情况 2 处理,O( n ) 枚举一下断点,将整个数列分割成前半部分和后半部分,如果某次分割下,前半部分和后半部分满足了情况 2 的话,那么显然答案就是 YES 了,相反,如果上面三种情况都不满足的话,答案就是 NO 了
时间复杂度为 O( n ) ,如果不计 unordered_map 的常数的话
代码:
#include<iostream>
#include<cstdio>
#include<string>
#include<ctime>
#include<cmath>
#include<cstring>
#include<algorithm>
#include<stack>
#include<climits>
#include<queue>
#include<map>
#include<set>
#include<sstream>
#include<cassert>
#include<bitset>
#include<unordered_map>
using namespace std;
typedef long long LL;
typedef unsigned long long ull;
const int inf=0x3f3f3f3f;
const int N=5e5+100;
int a[N],n,k;
LL sum[N],Xor[N],SUM,XOR;
unordered_map<int,int>mp;
int cnt1[N],cnt2[N];//cnt1[i]:1~i内有多少个不同的数,cnt2[i]:i~n内有多少个不同的数
bool check(int st)//检查以st为起点的滑动窗口是否满足条件
{
for(int i=st;i<=n;i+=k)
{
int l=i,r=i+k-1;
if(r<=n)
{
if(sum[r]-sum[l-1]!=SUM||(Xor[r]^Xor[l-1])!=XOR)
return false;
}
else return cnt2[l]==n-l+1;
}
return true;
}
bool solve()
{
if(k<n)//情况1和情况2
{
SUM=0,XOR=0;
for(int i=1;i<=k;i++)//O(k)处理一下1~k的xor和sum作为标准
{
SUM+=i;
XOR^=i;
}
for(int i=1;i<=k;i++)//枚举起点位置
{
if(cnt1[i-1]!=i-1)
break;
if(check(i))
return true;
}
}
else//情况2和情况3
{
for(int i=2;i<=n;i++)//枚举断点
if(cnt1[i-1]==i-1&&cnt2[i]==n-i+1)
return true;
}
return false;
}
void init()//预处理cnt1和cnt2
{
mp.clear();
cnt1[0]=0;
for(int i=1;i<=n;i++)
{
cnt1[i]=cnt1[i-1];
if(!mp[a[i]])
cnt1[i]++;
mp[a[i]]++;
}
mp.clear();
cnt2[n+1]=0;
for(int i=n;i>=1;i--)
{
cnt2[i]=cnt2[i+1];
if(!mp[a[i]])
cnt2[i]++;
mp[a[i]]++;
}
}
int main()
{
#ifndef ONLINE_JUDGE
// freopen("data.in.txt","r",stdin);
// freopen("data.out.txt","w",stdout);
#endif
// ios::sync_with_stdio(false);
int w;
cin>>w;
while(w--)
{
scanf("%d%d",&n,&k);
bool flag=true;//判断数列的每个元素是否位于1~k之间
for(int i=1;i<=n;i++)
{
scanf("%d",a+i);
if(a[i]<1||a[i]>k)
flag=false;
sum[i]=sum[i-1]+a[i];
Xor[i]=Xor[i-1]^a[i];
}
if(!flag)
{
puts("NO");
continue;
}
init();
if(solve())
puts("YES");
else
puts("NO");
}
return 0;
}