【GMOJ4486】最长公共子串

题目

题目链接:https://gmoj.net/senior/#main/show/4486

\(n,m\leq 2000,k\leq 10^5\)

思路

首先如果两个段有交,那么可以把这两个段合并起来。
然后对于 \(S\) 串中任意一个字符 \(c\),如果它没有被任何区间覆盖,那么可以再加入一个长度为 \(1\) 的区间覆盖它。
然后考虑 dp。设 \(f[i][j]\) 表示 \(S\) 串的第 \(i\) 个区间与 \(T\) 串的第 \(j\) 个字符开始,往后最多能匹配的位数。这个可以直接枚举求出,用桶来维护是否可行。
再设 \(g[i][j]\) 表示 \(S\) 串第 \(i\) 个区间到最后一个区间,与 \(T\) 串位置 \(j\) 开始,且这 \(f[i][j]\) 个字符必须全部匹配时,往后能最大匹配的位数。只需要判断一下第 \(i+1\) 个区间是否全部匹配上 \(T\) 即可。
然后枚举第 \(i\) 个区间匹配的长度求最大值即可。
时间复杂度 \(O(nm)\)

代码

#include <bits/stdc++.h>
using namespace std;

const int N=2010,M=110010,Inf=1e9;
int n,m,k,ans,cnt,bel[N],c[N][27],d[27],f[N][N],g[N][N];
char s[N],t[N];

struct node
{
	int l,r;
}a[M];

bool cmp(node x,node y)
{
	return x.l<y.l;
}

int main()
{
	freopen("lcs.in","r",stdin);
	freopen("lcs.out","w",stdout);
	scanf("%s%s%d",t+1,s+1,&k);
	n=strlen(s+1); m=strlen(t+1);
	for (int i=1;i<=k;i++)
	{
		scanf("%d%d",&a[i].l,&a[i].r);
		a[i].l++; a[i].r++;
	}
	for (int i=1;i<=n;i++)
		a[++k]=(node){i,i};
	sort(a+1,a+1+k,cmp);
	for (int i=1;i<=k;i++)
		if (a[i].l<=a[cnt].r) a[cnt].r=max(a[cnt].r,a[i].r);
			else a[++cnt]=a[i];
	k=cnt;
	for (int i=1;i<=k;i++)
		for (int j=a[i].l;j<=a[i].r;j++)
			bel[j]=i,c[i][s[j]-'a']++;
	for (int i=k;i>=1;i--)
		for (int j=m;j>=1;j--)
		{
			int len=a[i].r-a[i].l,l=j;
			for (;l<=min(j+len,m);l++)
			{
				if (c[i][t[l]-'a']==d[t[l]-'a']) break;
				d[t[l]-'a']++;
			}
			f[i][j]=l-j;
			if (f[i+1][l]==a[i+1].r-a[i+1].l+1)
				g[i][j]=f[i][j]+g[i+1][l];
			else
				g[i][j]=f[i][j]+f[i+1][l];
			for (int p=j;p<=l;p++)
			{
				if (p!=l) d[t[p]-'a']--;
				if (f[i+1][p]==a[i+1].r-a[i+1].l+1)
					ans=max(ans,p-j+g[i+1][p]);
				ans=max(ans,p-j+f[i+1][p]);
			}
		}
	cout<<ans;
	return 0;
}
posted @ 2021-07-12 16:17  stoorz  阅读(41)  评论(1编辑  收藏  举报