【无标题】P8986 [北大集训 2021] 基因编辑

Question 问题 P8986 [北大集训 2021] 基因编辑

给定一个长度为 n n n 的序列 a a a 以及需要切割的范围 l , r l,r l,r,求其中最短的合法子序列 ( x , y ) (x,y) (x,y) 满足:

  1. x < l    y > r xr x<l  y>r
  2. 不存在 ( x ′ , y ′ ) (x',y') (x,y) 满足 a x ′ = a x    a y ′ = a y a_{x'}=a_x~~a_{y'}=a_y ax=ax  ay=ay

Analysis 分析

令:

p r e i pre_i prei a i a_i ai 这个颜色在 i i i 前第一次出现的下标。

l s t i lst_i lsti a i a_i ai 这个颜色最后一次出现的下标。

由反证法易得:我们选出来的 x , y x,y x,y 只有可能是某种颜色的第一次出现的位置和最后出现的位置。同时还必须满足 p r e y < x pre_yprey<x y y y 在区间 ( r , n ] (r,n] (r,n] 只出现一次, x x x 在区间 [ 1 , l ) [1,l) [1,l) 只出现过一次。

Solution

从小到大枚举 y y y,开一个 set 维护维护在 [ 1 , l ) [1,l) [1,l) 出现一次的 x x x。到区间 ( r , n ] (r,n] (r,n] 选一个只出现过一次的 y y y 后,再从 set 中找到一个最大的下标并更新答案。记得判断无解。

Code 代码

int n,l,r,ans=inf;
int a[N],pre[N],lst[N],now[N];//如题解意思,now 是为了方便清除 set
int v[N];// v[i]=a[i] 在区间 (r,n] 第一次出现,用来判断是不是唯一出现。
set<int> s;
int main(){
	read(n,l,r);
	for(rint i=1;i<=n;i++){
		read(a[i]);
		pre[i]=lst[a[i]];lst[a[i]]=i;
		if(i>r && !v[a[i]]) v[a[i]]=i;
	}
	for(rint i=1;i<=n;i++){
		if(i<l && !now[a[i]]) s.insert(i);
		else if(s.find(now[a[i]])!=s.end()) s.erase(now[a[i]]);
		if(!now[a[i]]) now[a[i]]=i;
		if(i>r && v[a[i]]==i && i==lst[a[i]] && s.size()){
			int t=*(--s.end());
			if(t>=pre[i]) ans=min(ans,i-t+1);
		}
	}
	if(ans==inf) ans=-1;
	printf("%d\n",ans);
    return 0;
}

后记

该方法常数较大,请开 O2。如有不开 O2 的方法,请私信我谢谢。

你可能感兴趣的:(算法)