Codeforces Round #543 (Div. 2) D 双指针 + 模拟

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]分成两种情况:
    1. (r-l+1)>k:去掉(r-l+1-k)个不符合要求的数
    2. (r-l+1)==k:不用删除任何数

坑点

  • 对于第二段的第一种情况,不符合要求的数包括:
    1. 不在集合内的数
    2. 在集合内,但超出集合数量的数
    • 模拟时应该优先删除不在集合内的数,并统计数目,数目不够再删除在集合内的数,假如优先删除在集合内的数,有可能会导致[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;
}
原文地址:https://www.cnblogs.com/VIrtu0s0/p/10486509.html