• 后缀数组SA 回文自动机PAM


    后缀数组

    两个数组:\(sa[]\)\(rk[]\)

    \(sa[i]\)表示将所有后缀排第\(i\)小的后缀的编号(起始位置在哪里)
    \(rk[i]\)表示以\(i\)为起始位置的后缀的排名。

    这两个数组满足性质:\(sa[rk[i]]=rk[sa[i]]=i\)
    oiwiki讲的很好,直接粘过来
    oiwi

    正常排序,帮助理解求\(sa\)过程

    $nlog^2n$
    #include<cstdio>
    #include<cstring>
    #include<algorithm>
    using namespace std;
    typedef long long ll;
    typedef unsigned long long ull;
    const int inf=0x3f3f3f;
    const int maxn=1000005;
    char s[maxn];
    int n,w,sa[maxn],rk[maxn<<1|1],oldrk[maxn<<1|1];
    bool cmp(int x,int y){
        return rk[x]==rk[y]?rk[x+w]<rk[y+w]:rk[x]<rk[y];
        //以rank[i]为第一关键字,rank[i+w]为第二关键字
    }
    int main(){
        scanf("%s",s+1);
        n=strlen(s+1);
        for(int i=1;i<=n;++i)sa[i]=i;//随便给个1-n的排序
        for(int i=1;i<=n;++i)rk[i]=s[i];//先按照第一个字母排个rank,只需要相对大小即可
        for(w=1;w<n;w<<=1){
            sort(sa+1,sa+n+1,cmp);
            for(int i=1;i<=n;++i)oldrk[i]=rk[i];
            for(int p=0,i=1;i<=n;++i){
                if(oldrk[sa[i]]==oldrk[sa[i-1]]&&oldrk[sa[i]+w]==oldrk[sa[i-1]+w])rk[sa[i]]=p;
                else rk[sa[i]]=++p;
                //判断条件和p是为了去重
            }
        }
        for(int i=1;i<=n;++i)printf("%d ",sa[i]);
        return 0;
    }
    

    优化,基数排序

    不卡常$nlogn$
    #include<cstdio>
    #include<cstring>
    #include<algorithm>
    using namespace std;
    typedef long long ll;
    typedef unsigned long long ull;
    const int inf=0x3f3f3f;
    const int maxn=1000005;
    char s[maxn];
    int n,w,sa[maxn],rk[maxn<<1|1],id[maxn],m=300,oldrk[maxn<<1|1],cnt[maxn];
    int main(){
        scanf("%s",s+1);
        n=strlen(s+1);
        for(int i=1;i<=n;++i)++cnt[rk[i]=s[i]];//按照第一个字母排个序
        for(int i=1;i<=m;++i)cnt[i]+=cnt[i-1];
        for(int i=n;i>=1;--i)sa[cnt[rk[i]]--]=i;
        m=max(m,n);
        for(w=1;w<n;w<<=1){
            memset(cnt,0,sizeof(cnt));
            for(int i=1;i<=n;++i)id[i]=sa[i];
            for(int i=1;i<=n;++i)++cnt[rk[id[i]+w]];
            for(int i=1;i<=m;++i)cnt[i]+=cnt[i-1];
            for(int i=n;i>=1;--i)sa[cnt[rk[id[i]+w]]--]=id[i];//基数排序第二关键字
    
            memset(cnt,0,sizeof(cnt));
            for(int i=1;i<=n;++i)id[i]=sa[i];
            for(int i=1;i<=n;++i)++cnt[rk[id[i]]];
            for(int i=1;i<=m;++i)cnt[i]+=cnt[i-1];
            for(int i=n;i>=1;--i)sa[cnt[rk[id[i]]]--]=id[i];//基数排序第一关键字
    
            for(int i=1;i<=n;++i)oldrk[i]=rk[i];//与之前相同
            for(int p=0,i=1;i<=n;++i){
                if(oldrk[sa[i]]==oldrk[sa[i-1]]&&oldrk[sa[i]+w]==oldrk[sa[i-1]+w])rk[sa[i]]=p;
                else rk[sa[i]]=++p;
            }
        }
        for(int i=1;i<=n;++i)printf("%d ",sa[i]);
        return 0;
    }
    

    卡常+亿点注释

    卡常$nlogn$
    #include<cstdio>
    #include<cstring>
    #include<algorithm>
    using namespace std;
    typedef long long ll;
    typedef unsigned long long ull;
    const int inf=0x3f3f3f;
    const int maxn=1000005;
    char s[maxn];
    int n,w,sa[maxn],rk[maxn<<1|1],id[maxn],m=300,oldrk[maxn<<1|1],cnt[maxn],px[maxn];
    bool cmp(int x,int y,int w){
        return oldrk[x]==oldrk[y]&&oldrk[x+w]==oldrk[y+w];
    }
    int main(){
        scanf("%s",s+1);
        n=strlen(s+1);
        for(int i=1;i<=n;++i)++cnt[rk[i]=s[i]];//按照第一个字母排个序,这个时候只需要相对大小关系,里面的东西暂时不太合法
        for(int i=1;i<=m;++i)cnt[i]+=cnt[i-1];//桶累加
        for(int i=n;i>=1;--i)sa[cnt[rk[i]]--]=i;//cnt[..]为i的新rank 其实是sa[rk[i]]=i
        m=max(m,n);//m为桶的值域
    
        for(w=1;w<n;w<<=1){
            int p=0;
            for(int i=n;i>n-w;--i)id[++p]=i;//空串直接记录,按照第二关键字他们最小且没有顺序,所以随便给个顺序即可
            for(int i=1;i<=n;++i)if(sa[i]>w)id[++p]=sa[i]-w;//第二关键字,用后缀i的当前sa的顺序去更新后缀sa[i]-w
    
            for(int i=1;i<=m;++i)cnt[i]=0;//清桶
            for(int i=1;i<=n;++i)++cnt[px[i]=rk[id[i]]];//按照原来的rank排序,px临时存一下rk[id[i]],减少不必要的内存访问
            for(int i=1;i<=m;++i)cnt[i]+=cnt[i-1];//累加
            for(int i=n;i>=1;--i)sa[cnt[px[i]]--]=id[i];//cnt[..]为sa[i]的新rank 而为了避免改乱,所以原来的sa用id来代替,这就是上面和这里赋值用id的原因
            
            for(int i=1;i<=n;++i)oldrk[i]=rk[i];//copy一下,cmp用
            p=0;
            for(int i=1;i<=n;++i)rk[sa[i]]=cmp(sa[i],sa[i-1],w)?p:++p;//写cmp减少不必要的内存访问,去重
    
            if(p==n)break;//已经排完就不用管了
            m=p;//值域优化
        }
        for(int i=1;i<=n;++i)printf("%d ",sa[i]);
        return 0;
    }
    

    最长公共前缀\(LCP\)

    定义\(LCP(i,j)\) 表示\(sa_i\)个和\(sa_j\)个的两个后缀的最长公共前缀。

    性质

    \(LCP(i,j)=LCP(j,i)\)

    \(LCP(i,i)=len(sa_i)=n−sa_i+1\)

    \(LCP Lemma :\)

    \(LCP(i,j)=min⁡(LCP(i,k),LCP(k,j))(1≤i≤k≤j≤n)\)

    \(LCP Theorem:\)

    $ LCP(i,j)=min⁡(LCP(k,k−1))(1<i<k≤j≤n)$

    求法

    定义

    \(height[i]=lcp(sa[i],sa[i-1])\)

    引理:

    \(height[rk[i]]\ge height[rk[i-1]]-1\)

    比较感性的证明观察一下

    image

    LCP
    void LCP(){
        for(int i=1,k=0;i<=n;++i){
            if(rk[i]==0)continue;
            if(k)--k;//height[i]>=heigh[i-1]-1;
            while(s[i+k]==s[sa[rk[i]-1]+k])++k;
            height[rk[i]]=k;
        }
    }
    

    回文自动机PAM

    code
    #include<cstring>
    #include<iostream>
    using namespace std;
    const int maxn = 5e5+55;
    char s[maxn];
    int ch[maxn][27], len[maxn], fail[maxn], dep[maxn];
    int main(){
    	cin >> s + 1;
    	s[0] = '#';
    	int n = strlen(s + 1);
    	int las = 0, ans = 0, cnt = 1;
    	fail[0] = 1;len[1] = -1;
    	for(int i = 1 ; i <= n; ++i){
    		while(s[i - len[las] - 1] != s[i])las = fail[las];
    		if(!ch[las][s[i] - 'a']){
    			len[++cnt] = len[las] + 2;
    			int j = fail[las];
    			while(s[i - len[j] - 1] != s[i])j = fail[j];
    			fail[cnt] = ch[j][s[i] - 'a'];
    			ch[las][s[i] - 'a'] = cnt;
    		}
    		las = ch[las][s[i] - 'a'];
    		cout << (ans = dep[las]) << " " ;
    	}
    	return 0;
    }
    
  • 相关阅读:
    转:[Silverlight入门系列]使用MVVM模式(9): 想在ViewModel中控制TreeView节点展开?
    C#线程同步方法——Monitor
    转:Mongodb源码分析之Replication模式
    转:Mysql使用主从复制机制(replication)
    Ruby IDE
    转:ASP.NET MVC4细嚼慢咽---(5)js css文件合并
    转:ASP.NET MVC4细嚼慢咽---(6)全局过滤器
    转:WCF服务开发与调用的完整示例
    转:WF工作流技术内幕 —— 通过Web服务调用Workflow工作流(开发持久化工作流)
    汇总高效的卷积神经网络结构[转载]
  • 原文地址:https://www.cnblogs.com/Chencgy/p/16390419.html
Copyright © 2020-2023  润新知