「ARC151E」Keep Being Substring - 题解

题意

题目链接:Link

有一个序列 \(A\)\(X,Y\) 是给定的 \(A\) 的两个子串,每次可以在 \(X\) 的开头或末尾增添或删除一个数字,且需满足任意时刻 \(X\) 非空且为 \(A\) 的子串,求把 \(X\) 变成 \(Y\) 的最少次数。

题解

先考虑如果 \(X,Y\) 有公共字串的情况。不妨设 \(X,Y\) 的最长公共子串为 \(S\)

于是显然有一种方法,先将 \(X\) 一直删,直到剩下 \(S\),然后再添加补成 \(Y\),这样操作数是 \(|X| + |Y| - 2 |S|\)

求最长公共子串可以用 SA,复杂度 \(\mathcal{O}(n \log n)\)

当然也可以不用 SA。考虑哈希,然后二分一个长度,枚举 \(X\) 该长度的子串,然后用 map 存下 \(Y\),匹配即可。复杂度 \(\mathcal{O}(n \log^2 n)\)

然后考虑没有公共字串的情况。这意味着 \(X,Y\) 没有相同字符。

有一个贪心。

如果 \(X\)\(Y\) 没有公共字串,并且 \(|X| \ge 2\) 时,优先执行删除操作一定不劣。

根据这个贪心,我们可以一直删 \(X\),直到 \(|X|=1\),然后再加一个字符。如果依然没有公共串,那么就再删一个字符。

于是当 \(X,Y\) 有公共字串的时候,一定是长度为 \(1\) 的串。那么有公共串后就变成上面那个问题,答案为 \(|X|+|Y|-2\)

然后问题就是最后选哪个字符为公共串。考虑建图,对于 \(A\),将相邻的两个点连一条边权为 \(1\) 的边。于是问题就是以 \(A\) 中所有出现在 \(X\) 的字符为起点,以 \(A\) 中所有出现在 \(Y\) 中的字符为终点,求最短路。直接 bfs 即可。

最短路跑出来的最近的公共字符点中的路径,是要先加进来再删掉的,所以操作数为路径长度的 \(2\) 倍。

那么为什么这个贪心是正确的?

因为不存在公共串,所以最开始出现在 \(|X|\) 的串中的字符一定是要被删掉的。然后我们会找到一个最近的公共字符的位置 \(p\),即使我们先不删除 \(p\) 到原先串之间的路径,之后也需要删除到只剩下公共串,也就是位置 \(p\)

但是否会出现串到位置 \(p\) 之间的字符出现,先被删,再加进来搭路到 \(p\),再删掉的情况?可以发现,如果存在这种情况,那么这个操作数一定不是最短路径。

复杂度 \(\mathcal{O}(n \log n)\)

代码

#include<iostream>
#include<cstdio>
#include<cmath>
#include<cstring>
#include<queue>
using namespace std;
const int N=4e5+5;
struct edge{
	int v,nx;
}e[N<<1];
int n,nb,nc,cnt,a[N],b[N],c[N],s[N],sa[N],height[N],rk[N];
int len,ans,ne,f[N],deep[N];
bool vb[N],vc[N];
queue<int> q;
void SA()
{	static int m=n+1,x[N],y[N],cc[N];
	for(int i=0;i<=max(m,cnt);i++)x[i]=y[i]=cc[i]=0;
	for(int i=1;i<=cnt;i++)x[i]=s[i],cc[x[i]]++;
	for(int i=1;i<=m;i++)cc[i]+=cc[i-1];
	for(int i=cnt;i>=1;i--)sa[cc[x[i]]--]=i;
	for(int k=1;k<=cnt;k<<=1)
	{	int num=0;
		for(int i=cnt-k+1;i<=cnt;i++)y[++num]=i;
		for(int i=1;i<=cnt;i++)
			if(sa[i]>k)y[++num]=sa[i]-k;
		for(int i=1;i<=m;i++)cc[i]=0;
		for(int i=1;i<=cnt;i++)cc[x[i]]++;
		for(int i=2;i<=m;i++)cc[i]+=cc[i-1];
		for(int i=cnt;i>=1;i--)sa[cc[x[y[i]]]--]=y[i],y[i]=0;
		swap(x,y),num=1,x[sa[1]]=1;
		for(int i=2;i<=cnt;i++)
		{	if(y[sa[i]]==y[sa[i-1]]&&y[sa[i]+k]==y[sa[i-1]+k])x[sa[i]]=num;
			else x[sa[i]]=++num;
		}
		if(num==cnt)break;
		m=num;
	}
}
void LCP()
{	int k=0;
	for(int i=1;i<=cnt;i++)rk[sa[i]]=i;
	for(int i=1;i<=cnt;i++)
	{	if(rk[i]==1)continue;
		if(k)k--;
		int j=sa[rk[i]-1];
		while(i+k<=cnt&&j+k<=cnt&&s[i+k]==s[j+k])k++;
		height[rk[i]]=k;
	}
}
int solve()
{	int res=0;
	for(int i=2;i<=cnt;i++)
		if((sa[i-1]<=nb&&sa[i]>nb)||(sa[i-1]>nb&&sa[i]<=nb))res=max(res,height[i]);
	return res;
}
void read(int u,int v)
{	e[++ne].v=v;
	e[ne].nx=f[u];
	f[u]=ne;
}
int main()
{	scanf("%d",&n);
	for(int i=1;i<=n;i++)scanf("%d",&a[i]);
	scanf("%d",&nb);
	for(int i=1;i<=nb;i++)
	{	scanf("%d",&b[i]);
		s[++cnt]=b[i],vb[b[i]]=1;
	}
	s[++cnt]=n+1; 
	scanf("%d",&nc);
	for(int i=1;i<=nc;i++)
	{	scanf("%d",&c[i]);
		s[++cnt]=c[i],vc[c[i]]=1;
	}
	SA(),LCP(),len=solve(),ans=n;
	if(len>0){printf("%d\n",nb+nc-(len<<1));return 0;}
	for(int i=1;i<n;i++)read(a[i],a[i+1]),read(a[i+1],a[i]);
	for(int i=1;i<=n;i++)
		if(vb[i])q.push(i),deep[i]=1;
	while(!q.empty())
	{	int u=q.front();q.pop();
		if(vc[u]){ans=deep[u]-1;break;}
		for(int i=f[u];i;i=e[i].nx)
		{	int v=e[i].v;
			if(!deep[v])q.push(v),deep[v]=deep[u]+1;
		}
	}
	printf("%d\n",(ans<<1)+nb+nc-2);
	return 0;
}
posted @ 2022-10-24 20:33  Rainy7  阅读(60)  评论(0编辑  收藏  举报