P8986 [北大集训 2021] 基因编辑
Question 问题 P8986 [北大集训 2021] 基因编辑
给定一个长度为 \(n\) 的序列 \(a\) 以及需要切割的范围 \(l,r\),求其中最短的合法子序列 \((x,y)\) 满足:
- \(x<l~~y>r\)
- 不存在 \((x',y')\) 满足 \(a_{x'}=a_x~~a_{y'}=a_y\)
Analysis 分析
令:
\(pre_i\) 为 \(a_i\) 这个颜色在 \(i\) 前第一次出现的下标。
\(lst_i\) 为 \(a_i\) 这个颜色最后一次出现的下标。
由反证法易得:我们选出来的 \(x,y\) 只有可能是某种颜色的第一次出现的位置和最后出现的位置。同时还必须满足 \(pre_y<x\) 且 \(y\) 在区间 \((r,n]\) 只出现一次,\(x\) 在区间 \([1,l)\) 只出现过一次。
Solution
从小到大枚举 \(y\),开一个 set
维护维护在 \([1,l)\) 出现一次的 \(x\)。到区间 \((r,n]\) 选一个只出现过一次的 \(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
的方法,请私信我谢谢。