牛客多校6 - K-Bag(哈希+滑动窗口)

题目链接:点击查看

题目大意:k-bag 序列的定义是由多个 1 ~ k 的排列顺序连接起来的序列,现在问给定的序列是不是 k-bag 的连续子串

题目分析:读完题目后,如果给定的序列是 k-bag 的连续子串的话,那么可以将所有情况都分为以下三种类型:

  1. 一个部分k-bag的前缀 + 数个(至少一个)完整的k-bag + 一个部分k-bag的后缀
  2. 一个部分k-bag的前缀 + 一个部分k-bag的后缀
  3. 一个部分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;
}

猜你喜欢

转载自blog.csdn.net/qq_45458915/article/details/107624244