https://codeforces.com/contest/1121/problem/D
题意
给你一个m(<=5e5)个数的序列,选择删除某些数,使得剩下的数按每组k个数以此分成n组(n*k<=m),存在只要一组满足和目标集合s(|s|<=k)匹配(即集合中存在的数,组内一定存在)
题解
- 重点:找出至少一组满足要求的数
- 假设[l,r]内满足要求,还需要满足:((l-1)/k*k+(m-r)/k*k>=k*(n-1)),可以用双指针,对于每个l可以处理出最小的r满足要求
- 这样就把数组分成了三段[1,l-1],[l,r],[r+1,m],第一段[1,l-1]删除数字使得可以被k整除(没有限制随便删除),第二段[l,r]分成两种情况:
- (r-l+1)>k:去掉(r-l+1-k)个不符合要求的数
- (r-l+1)==k:不用删除任何数
坑点
- 对于第二段的第一种情况,不符合要求的数包括:
- 不在集合内的数
- 在集合内,但超出集合数量的数
- 模拟时应该优先删除不在集合内的数,并统计数目,数目不够再删除在集合内的数,假如优先删除在集合内的数,有可能会导致[l,r]内不符合要求
代码
#include<bits/stdc++.h>
#define M 500005
using namespace std;
int m,k,n,s,a[M],mp[M],x,kd,cnt,vi[M],l,r,ed,i,rm,CNT;
map<int,int>mk;
vector<int>ans;
int main(){
cin>>m>>k>>n>>s;
for(i=1;i<=m;i++)scanf("%d",&a[i]);
for(i=1;i<=s;i++){
scanf("%d",&x);
if(!mp[x]){kd++;}
mp[x]++;
}
cnt=0;
for(l=1;l<=m;){
while(cnt<kd&&r<=m){
vi[a[++r]]++;
if(vi[a[r]]==mp[a[r]])cnt++;
//if(vi[r]==mp[x]+1)cnt--;
}
if(r>m)break;
if(cnt==kd){
ed=max(l+k-1,r);
if(ed<=m){
if((l-1)/k+(m-ed)/k>=n-1){
rm=(l-1)%k;
for(i=1;i<=rm;i++)ans.push_back(i);
mk.clear();
if(r>l+k-1){
CNT=r-(l+k-1);
for(i=l;i<=ed;i++){
if(mp[a[i]]&&mk[a[i]]<mp[a[i]]){
mk[a[i]]++;
}else if(CNT){
ans.push_back(i);
CNT--;
}
}
}
printf("%d
",ans.size());
for(i=0;i<ans.size();i++)
printf("%d ",ans[i]);
return 0;
}
}
}
if(vi[a[l]]==mp[a[l]])cnt--;
vi[a[l++]]--;
}
cout<<-1;
}