【洛谷P4022】熟悉的文章

题目

题目链接:https://www.luogu.com.cn/problem/P4022
阿米巴是小强的好朋友。
在小强眼中,阿米巴是一个作文成绩很高的文艺青年。为了获取考试作文的真谛,小强向阿米巴求教。阿米巴给小强展示了几篇作文,小强觉得这些文章怎么看怎么觉得熟悉,仿佛是某些范文拼拼凑凑而成的。小强不禁向阿米巴投去了疑惑的眼光,却发现阿米巴露出了一个狡黠的微笑。
为了有说服力地向阿米巴展示阿米巴的作文是多么让人觉得“眼熟”,小强想出了一个评定作文 “熟悉程度”的量化指标:(L_0) .小强首先将作文转化成一个 (01) 串。之后,小强搜集了各路名家的文章,同样分别转化成 (01) 串后,整理出一个包含了 (M)(01) 串的 “ 标准作文库 ”。
小强认为:如果一个 (01) 串长度不少于 (L) 且在标准作文库中的某个串里出现过(即,它是标准作文库的某个串的一个 连续子串),那么它是 “ 熟悉 ” 的。对于一篇作文(一个 (01) 串)(A),如果能够把 (A) 分割成若干段子串,其中 “ 熟悉 ” 的子串的长度总和不少于 (A) 总长度的 (90\%),那么称 (A) 是 “ 熟悉的文章 ”。 (L_0) 是能够让 (A) 成为 “ 熟悉的文章 ” 的 所有 (L) 的最大值 (如果不存在这样的 (L),那么规定 (L_0=0))。
举个例子:
小强的作文库里包含了如下 (2) 个字符串:

10110
000001110

有一篇待考察的作文是:

1011001100

小强计算出这篇作文 (L) 的最大值是 (4),因为待考察的作文可以视作 (10110+0110+0),其中 (10110)(0110) 被判定为 “熟悉” 的。而当 (L = 5) 或是更大的时候,不存在符合题意的分割方法。所以,这篇作文的 (L_0 = 4)。小强认为阿米巴作文的 (L_0) 值比其他同学的明显要大。请你帮他验证一下。

输入文件大小不超过 (1.1 imes 10^6) 字节。

思路

首先肯定二分答案。考虑如果判断一个答案是否可行。
(f[i]) 表示前 (i) 个字符在目前二分的 (L) 下,最多能匹配的字符数量。
那么有转移

[f[i]=max(f[i-1],f[j]+i-j) (jin[i-g[i],i-L]) ]

其中 (g[i]) 表示第 (i) 个字符最多往前多少位的子串是可以匹配上的。这个东西可以建出广义 SAM 后对于每一次询问的字符串求一次。
观察到 (i-g[i]) 是单调不降的,所以可以用一个单调队列优化 dp 转移。
时间复杂度 (O(sum |s|+sum |t|))

代码

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

const int N=2200010;
const double eps=1e-8;
int n,m,f[N],g[N];
char s[N];
deque<int> q;

struct SAM
{
	int last,tot,fa[N],len[N],ch[N][2];
	SAM() { last=tot=1; }
	
	void insert(int c)
	{
		int p=last;
		if (ch[p][c])
		{
			int q=ch[p][c];
			if (len[q]==len[p]+1) last=q;
			else
			{
				int nq=++tot; last=nq;
				len[nq]=len[p]+1; fa[nq]=fa[q]; 
				ch[nq][0]=ch[q][0]; ch[nq][1]=ch[q][1];
				fa[q]=nq;
				for (;p && ch[p][c]==q;p=fa[p]) ch[p][c]=nq;
			}
		}
		else
		{
			int np=++tot; last=np;
			len[np]=len[p]+1;
			for (;p && !ch[p][c];p=fa[p]) ch[p][c]=np;
			if (!p) fa[np]=1;
			else
			{
				int q=ch[p][c];
				if (len[q]==len[p]+1) fa[np]=q;
				else
				{
					int nq=++tot;
					len[nq]=len[p]+1; fa[nq]=fa[q];
					ch[nq][0]=ch[q][0]; ch[nq][1]=ch[q][1];
					fa[np]=fa[q]=nq;
					for (;p && ch[p][c]==q;p=fa[p]) ch[p][c]=nq;
				}
			}
		}
	}
	
	void match(char *s)
	{
		int le=strlen(s+1),p=1;
		for (int i=1;i<=le;i++)
		{
			int c=s[i]-48;
			if (ch[p][c]) p=ch[p][c],g[i]=g[i-1]+1;
			else
			{
				while (p && !ch[p][c]) p=fa[p];
				g[i]=len[p]+1; p=p?ch[p][c]:1;
			}
		}
	}
}sam;

bool check(int mid,int len)
{
	while (q.size()) q.pop_back();
	for (int i=1;i<=len;i++)
	{
		f[i]=f[i-1];
		while (q.size() && q.front()<i-g[i]) q.pop_front();
		if (q.size()) f[i]=max(f[i],f[q.front()]+i-q.front());
		if (i-mid+1>=0)
		{
			int j=i-mid+1;
			while (q.size() && f[q.back()]-q.back()<=f[j]-j) q.pop_back();
			q.push_back(j);
		}
	}
	return f[len]>(int)(0.9*len-eps);
}

int main()
{
	scanf("%d%d",&n,&m);
	for (int i=1;i<=m;i++)
	{
		scanf("%s",s+1);
		int len=strlen(s+1); sam.last=1;
		for (int j=1;j<=len;j++)
			sam.insert(s[j]-48);
	}
	while (n--)
	{
		scanf("%s",s+1);
		sam.match(s);
		int len=strlen(s+1),l=1,r=len,mid;
		while (l<=r)
		{
			mid=(l+r)>>1;
			if (check(mid,len)) l=mid+1;
				else r=mid-1;
		}
		cout<<l-1<<"
";
	}
	return 0;
}
原文地址:https://www.cnblogs.com/stoorz/p/15154839.html