• 【洛谷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;
    }
    
  • 相关阅读:
    Chapter 5. 集合---(ArrayList、Stack、queue、哈希表)
    Chapter 4. 数组
    第一章 代码无错就是优?——简单工厂模式
    条款34:区分接口继承和实现继承
    条款33:避免遮掩继承而来的名称
    条款32:确定你的public继承塑模出is-a关系
    条款12:复制对象时勿忘其每一部分
    A:魔兽世界之一:备战
    条款09:绝不在构造和析构过程中调用virtual函数
    7、数值算法
  • 原文地址:https://www.cnblogs.com/stoorz/p/15154839.html
Copyright © 2020-2023  润新知