题意
题目链接: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;
}
标签:子串,字符,Being,题解,Substring,int,公共,sa,include
From: https://www.cnblogs.com/Rainy7/p/solution-at-arc151e.html