zoukankan      html  css  js  c++  java
  • 【洛谷P3804】【模板】后缀自动机 (SAM)

    题目

    题目链接:https://www.luogu.com.cn/problem/P3804
    给定一个只包含小写字母的字符串(S),
    请你求出 (S) 的所有出现次数不为 (1) 的子串的出现次数乘上该子串长度的最大值。
    (|S|leq 10^6)

    思路

    推荐 BlogOIwiki
    SAM 可以在线性复杂度内解决一些关于子串的问题。例如经典的本质不同子串个数。
    我们定义 (mathrm{endpos}(t)) 表示子串 (t) 在原串 (s) 中出现位置集合。具体的,

    [mathrm{endpos}(t)={x|s_{x-|t|+1}cdots s_x=t} ]

    对于 (mathrm{endpos}(t)) 相同的所有子串 (t),我们把他们归到一个等价类中,然后对于每一个等价类建立一个节点。
    如果 (mathrm{endpos}(t)⫋mathrm{endpos}(t')),且不存在任意 (t''(|t''|>|t'|)) 满足 (mathrm{endpos}(t)⫋mathrm{endpos}(t'')),那么我们就从 (t') 所在类的节点向 (t) 所在类的节点连一条边。
    容易发现,连边结束后,所有的点构成了一棵树,我们称其为 parent 树。可以证明树上的点数是 (O(n)) 的。
    我们记 (mathrm{len}(x)) 表示节点 (x) 所在的等价类中,长度最长的子串的长度,(mathrm{minlen}(x)) 表示最短长度。那么容易发现 (mathrm{minlen}(x)=mathrm{len}(fa_x)+1)。因为显然在原串中它们是包含关系且只差一个字符。
    一个后缀自动机(SAM)的节点和 parent 树完全一致,但是连边方式不同。在 SAM 中,一个类所对应的节点会向另一个类连一条有向边,当且仅当在这个类的任意子串的末尾添加一个字符 (c),得到的串的 (mathrm{endpos}) 集合等于后者所在的类的 (mathrm{endpos}) 集合。那么这条有向边的权值就为这个字符 (c)
    依旧是可以证明,SAM 的边数是 (O(n)) 的。具体可以看上面推荐的文章。wtcl。
    然后利用 parent 树和后缀自动机,就可以解决字符串的很多问题。但是为了保证复杂度,如果需要排序,那么要采用基数排序。


    回到本题,建出 parent 树后,容易发现一个点所表示的等价类中,所有子串出现的次数都等于 parent 树上这个节点的子树大小。
    那么直接求出每一个节点的大小,乘上 (mathrm{len_x}) 取最大值即可。
    时间复杂度 (O(n))

    代码

    #include <bits/stdc++.h>
    using namespace std;
    typedef long long ll;
    
    const int N=2000010;
    int n,a[N],cnt[N];
    ll ans,f[N];
    char s[N];
    
    struct SAM
    {
    	int tot,last,ch[N][26],fa[N],len[N];
    	SAM() { tot=last=1; }
    
    	void ins(int c)
    	{
    		int np=++tot,p=last; 
    		len[np]=len[p]+1; last=np; f[np]++;
    		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];
    				for (int i=0;i<26;i++) ch[nq][i]=ch[q][i];
    				fa[q]=fa[np]=nq;
    				for (;p && ch[p][c]==q;p=fa[p]) ch[p][c]=nq;
    			}
    		}
    	}
    	
    	void topsort()
    	{
    		for (int i=1;i<=tot;i++) cnt[len[i]]++;
    		for (int i=1;i<=tot;i++) cnt[i]+=cnt[i-1];
    		for (int i=tot;i>=1;i--) a[cnt[len[i]]--]=i;
    		for (int i=tot;i>=1;i--)
    		{
    			if (f[a[i]]>1) ans=max(ans,f[a[i]]*len[a[i]]);
    			f[fa[a[i]]]+=f[a[i]];
    		}
    	}
    }sam;
    
    int main()
    {
    	scanf("%s",s+1);
    	n=strlen(s+1);
    	for (int i=1;i<=n;i++)
    		sam.ins(s[i]-'a');
    	sam.topsort();
    	printf("%lld
    ",ans);
    	return 0;
    }
    
  • 相关阅读:
    C语言基本语法——函数
    C语言基本语法——数组
    iOS面试总结(待完善)
    iOS开发——蓝牙开发
    iOS开发——性能分析
    ios开发——runtime
    Extjs6的常见问题及解决办法
    会员信息布局,自动放缩
    LINQ关于NULL的怪现象
    [代码整洁]自我感悟
  • 原文地址:https://www.cnblogs.com/stoorz/p/14252912.html
Copyright © 2011-2022 走看看