洛谷P4022熟悉的文章

Posted stoorz

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了洛谷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\\times 10^6\\) 字节。

思路

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

\\[f[i]=\\max(f[i-1],f[j]+i-j)\\ (j\\in[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<<"\\n";
	}
	return 0;
}

以上是关于洛谷P4022熟悉的文章的主要内容,如果未能解决你的问题,请参考以下文章

Luogu P4022 [CTSC2012]熟悉的文章

P4022 [CTSC2012]熟悉的文章(广义SAM+决策单调性)

改数(洛谷 U5398)

片段(Java) | 机试题+算法思路+考点+代码解析 2023

markdown 打字稿...编码说明,提示,作弊,指南,代码片段和教程文章

VMPFC可以融合既有的片段信息来模拟出将来的情感场景