原题
题目描述
当一个数列可以表示为若干个1到k的排列依次组成时,这个数列被称为k-bag。例如1,2,3,2,1,3,3,2,1是一个3-bag。
如果一个序列是一个k-bag的连续子串,则其称为part-k-bag。
求一个长度为n的序列是否是一个part-k-bag。
第一行包含一个整数T(1≤T≤20),表示测试用例的数量。
然后是T个样例。每个测试案例的第一行包含两个整数n,k (1≤n≤5⋅105,1≤k≤109)。
每个测试案例的第二行包含n个整数表示序列。保证
∑
∑
∑n≤2⋅106,序列的值在
1到109之间。
如果一个序列是部分k-bag序列,则打印“是”,否则打印“否”。
样例
输入
1
8 3
2 3 2 1 3 3 2 1
输出
YES
思路
这是官方题解,大家可以先看看。
本题可以看成一个放隔板的问题,每个相同数字间都要放一个隔板。我们把隔板看成一个个区间,若有两个区间相互没有交集,则表示它不是part-k-bag。
因为序列的值在
1
1
1~
1
e
9
1e9
1e9之间,直接开数组肯定存不下,所以要用离散化。
我们可以先求出每个节点上放有多少块隔板。先把区间往前移动xk个单位,使区间的左端点在0到k之间。然后在左端点加一,右端点减一,最后再求一下前缀和即可。若有一处的隔板数量是离散化的最大值,则表示所有的隔板都可以放在上面,即任意的两个区间都有交集。
特殊情况:若序列中某个元素的值大于k,则这个序列肯定是不合法的。
区间移动的时候如果直接取模,则会出现有两种情况,具体可以看看代码。
代码
#include<bits/stdc++.h>
using namespace std;
const int maxn=5e5+5;
int a[maxn],b[maxn],c[maxn],d[maxn],cnt,sum,t,n,k;
int main()
{
for(scanf("%d",&t);t--;)
{
bool flag=1;d[0]=0;cnt=0;
scanf("%d%d",&n,&k);
for(int i=1;i<=n;i++)
{
scanf("%d",&a[i]);
if(a[i]>k)flag=0;
b[i]=a[i];c[i]=d[i]=0;
}
if(!flag){puts("NO");continue;}
sort(b+1,b+n+1);sum=unique(b+1,b+n+1)-b-1;//离散化
for(int i=1;i<=n;i++)a[i]=lower_bound(b+1,b+sum+1,a[i])-b;
for(int i=1;i<=n;i++)//移动区间并标记
{
if(c[a[i]]&&c[a[i]]>i-k)
{
d[c[a[i]]%k]++,cnt++;
if(c[a[i]]%k<i%k) d[i%k]--;
//移动后左端点在右端点的左边
else d[min(k,n+1)]--,d[0]++,d[i%k]--;
//移动后左端点在右端点的右边
}
c[a[i]]=i;
}
if(d[0]==cnt){printf("YES\n");continue;}
for(int i=1;i<=min(n,k-1);i++)//求前缀和
{
d[i]+=d[i-1];
if(d[i]==cnt){flag=0;break;}
}
if(!flag)puts("YES");
else puts("NO");
}
return 0;
}