zoukankan      html  css  js  c++  java
  • 字符串整理

    字符串整理(Manachar,KMP,扩展KMP,ACAM,SA,SAM,最小表示法)


    基础

    • 字符集(sum):一个字符集是一个建立了全序关系的集合,即任意属于(sum)的元素可以比较,字符集中的元素叫做字符
    • 字符串:一个字符串(S)将n个字符顺次排列组成,(n)(S)的长度,计作(|S|),此文使用的字符串均从1下标开始
    • 子串:字符串(S)的子串(S[i...j],i leq j),表示(S)串中从(i)(j)这一段,也就是顺次排列(S[i],S[i+1],...,S[j])形成的字符串。
    • 子序列:字符串(S)的子序列是从(S)中将若干元素提取出来并不改变相对位置形成的序列,即(S[p_1],S[p_2],...,S[p_t]),(1 leq p_1 leq p_1 leq ... leq p_t leq |S|)
    • 后缀:是指从某个位置(i)开始到整个串末尾结束的一个特殊子串。字符串(S)的从(i)开头的后缀表示为(Suffix(S,i)),也就是(Suffix(S,i)=S[i...|S|]),(suf(S,k)表示S后k个字符组成的后缀)
    • 前缀:是指从串首开始到某个位置(i)结束的一个特殊子串。字符串(S)的从(i)结尾的前缀表示为(Preffix(S,i)),也就是(Preffix(S,i)=S[1...i]),(pre(S,k)表示S前k个字符组成的前缀)
    • (Borde)r:(0 le r le |S|),(pre(S,r)=suf(S,r))(pre(S,r))为s的(Border)
      (Border有)传递性,(Border)(Border)还是原串的(Border)
    • 周期:(0 le p le |S|)(S[i]=S[i+p],forall i in{1,2,...,|S|-p}),p为s的周期
      (pre(S,k))(S)(Border)(Leftrightarrow) (|S|-k)(S)的周期
    • 自动机

    KMP

    • (nwxt[i])表示(s[1..i])的最大的(Border)
    • 根据(Border)的传递性,我们记录s最大的(Border)(mb(S)),s的所有(Border)(mb(S),mb(mb(S)),...)
    • 求得时候运用border传递性与以求信息
    • 可以(O(N))进行单串的匹配
    #include <bits/stdc++.h>
    using namespace std;
    
    const int MAXN = 1000001;
    
    char t[MAXN], s[MAXN];
    int n, m;
    int nxt[MAXN], k, ans, sh[MAXN];
    
    int main() {
    	
    	scanf("%s", t + 1); 
        scanf("%s", s + 1);
    
        n = strlen(s + 1); m = strlen(t + 1);
        nxt[0] = -1; nxt[1] = 0;
        for (int i = 2; i <= n; i++) {
            k = nxt[i - 1];
            while(k != -1 && s[k + 1] != s[i]) {
                k = nxt[k];
            }
            nxt[i] = k + 1;
        }
    
        ans = 0; k = 0;
        for (int i = 1; i <= m; i++) {
            while(k != -1 && s[k + 1] != t[i]) {
                k = nxt[k];
            }
            k = k + 1;
            if(k == n) ans++, sh[ans] = i - n + 1;
        }
    
        for (int i = 1; i <= ans; i++) printf("%d
    ", sh[i]);
        for (int i = 1; i <= n; i++) printf("%d ", nxt[i]);
            
    	return 0;
    }
    

    扩展KMP

    • 对于字符串(S)(Z_i)表示(lcp(Suffix(S,i),S))
    • 在求解时充分利用已有信息(与manachar有相似之处)
    • 做i的时候,记录1..i-1中匹配到的lcp到达最右边的位置
    #include <bits/stdc++.h>
    const int N = 20000007;
    int n, m, z[N], ext[N];
    char s[N], t[N];
    long long ans;
    int min(int x, int y) {
    	return x < y ? x : y;
    }
    inline void getz() {
    	z[1] = n;
    	ans ^= (1ll * 1 * (z[1] + 1));
    	for (register int i = 2, l = 0, r = 0; i <= n; i++) {//r 维护当前匹配过的子串中达到的最右边的位置
    		if(i <= r) z[i] = min(z[i - l + 1], r - i + 1);
    		while(i + z[i] <= n && s[i + z[i]] == s[z[i] + 1]) ++z[i];
    		if(i + z[i] - 1 > r) l = i, r = i + z[i] - 1;
    		ans ^= (1ll * i * (z[i] + 1));
    	}
    }
    inline void exkmp() {
    	for (register int i = 1, l = 0, r = 0; i <= m; i++) {
    		if(i <= r) ext[i] = min(z[i - l + 1], r - i + 1);
    		while(i + ext[i] <= m && t[i + ext[i]] == s[ext[i] + 1]) ++ext[i];
    		if(i + ext[i] - 1 > r) l = i, r = i + ext[i] - 1;
    		ans ^= (1ll * i * (ext[i] + 1));
    	}
    }
    int main() {
    	std::ios::sync_with_stdio(0);
    	std::cin.tie(0);
    	std::cin >> (t + 1);
    	std::cin >> (s + 1);
    	n = strlen(s + 1);
    	m = strlen(t + 1);
    	ans = 0;
    	getz(); std::cout << ans << std::endl;
    	ans = 0;
    	exkmp(); std::cout << ans << std::endl;
    	return 0;
    }
    

    Manachar

    • 用来寻找最长回文子串
    • 做的时候利用已有信息,记录当前曾到过的最右侧
    #include <bits/stdc++.h>
    using namespace std;
    int const maxn = 11000000;
    char s[maxn << 1];
    int cnt;
    int hw[maxn << 1], maxright, mid;
    int ans;
    inline void read() {
    	char c = getchar();
    	s[0] = '#'; s[cnt = 1] = '#';
    	while(c > 'z' || c < 'a') c = getchar();
    	while(c <= 'z' && c >= 'a') s[++cnt] = c, s[++cnt] = '#', c = getchar();
    }
    void manacher() {
    	for (int i = 1; i <= cnt; i++) {
    		if(i <= maxright) hw[i] = min(hw[(mid << 1) - i], maxright - i + 1);
    		else hw[i] = 1;
    		while(s[hw[i] + i] == s[i - hw[i]]) hw[i]++;
    		if(hw[i] + i - 1 >= maxright) maxright = hw[i] + i - 1, mid = i;
    		ans = max(ans, hw[i]);
    	}
    	
    }
    int main() {
    	read();
    	manacher();
    	printf("%d", ans - 1);
    	return 0;
    }
    

    Trie

    #include <bits/stdc++.h>
    using namespace std;
    const int MAXN = 1e5;
    int Trie[MAXN][30], tot = 1;
    int en[MAXN];
    void Insert(char* str) {
      int len =  strlen(str), p = 1;
      for (int i = 0; i < len; i++) {
        int ch = str[i] - 'a';
        if(Trie[p][ch] == 0) Trie[p][ch] = ++tot;
        p = Trie[p][ch];
      }
      en[p] = 1;
      return;
    }
    bool search(char* str) {
      int len = strlen(str), p = 1;
      for (int i = 0; i < len; i++) {
        int ch = str[i] - 'a';
        p = Trie[p][ch];
        if(p == 0) return 0;
      }
      return en[p];
    }
    int main() {
      return 0;
    }
    

    ACAM

    • AC自动机是一个形式上建立在trie上,实则是一个接受所有模式串的自动机
    • fail指针:fail(x)指向状态x的最长可识别后缀的状态位置
    • 求fail指针的时候可以沿当前节点往父亲节点跳,通过祖先节点来转移
    • 更方便的做法是用队列来逐层来求
    • 求出后应当建立出fail树,暴力跳fail指针效率较低
    #include<bits/stdc++.h>
    #define fi first
    #define se second
    #define pb push_back
    #define mp make_pair
    #define SZ(x) ((int)x.size())
    #define ALL(x) x.begin(),x.end()
    #define U(i,u) for(register int i=head[u];i;i=nxt[i])
    #define rep(i,a,b) for(register int i=(a);i<=(b);++i)
    #define per(i,a,b) for(register int i=(a);i>=(b);--i)
    using namespace std;
    typedef long double ld;
    typedef long long ll;
    typedef unsigned int ui;
    typedef pair<int,int> PII;
    typedef vector<int> VI;
    template<class T> inline void read(T &x){
    	x=0;char c=getchar();int f=1;
    	while(!isdigit(c)){if(c=='-')f=-1;c=getchar();}
    	while(isdigit(c)){x=x*10+c-'0';c=getchar();}x*=f;
    }
    template<class T> inline void cmin(T &x, T y){x=x<y?x:y;}
    template<class T> inline void cmax(T &x, T y){x=x>y?x:y;}
    const int N=5000010;
    int n,match[N];
    char s[N];
    struct ACAM{
    	int root,size,fail[N],to[N][27],head[N],nxt[N],v[N],cnt;
    	inline void add(int x,int y){nxt[++cnt]=head[x];head[x]=cnt;v[cnt]=y;}
    	inline int tn(int x,int y){
    		return to[x][y]?to[x][y]:tn(fail[x],y);
    	}
    	inline void insert(char str[],int id){
    		int len=strlen(str+1);
    		int p=root;
    		rep(i,1,len){
    			int c=str[i]-'a'+1;
    			if(!to[p][c])to[p][c]=++size;
    			p=to[p][c];
    		}
    		match[id]=p;
    	}
    	inline void build(){
    		size=root=1;rep(i,1,n){scanf("%s",s+1);insert(s,i);}
    	}
    	inline void getfail(){
    		queue<int>q;rep(i,1,26)to[0][i]=1;q.push(1);
    		while(q.size()){
    			int u=q.front();q.pop();
    			rep(i,1,26){
    				if(to[u][i]){
    					fail[to[u][i]]=tn(fail[u],i);q.push(to[u][i]);
    				}
    			}
    		}
    	}
    	inline void buildfail(){rep(i,2,size){add(fail[i],i);}}
    }acam;
    int main(){
    	return 0;
    }
    

    SA

    #include<bits/stdc++.h>
    const int N=1000010;
    char s[N];
    int n,m,num;
    int sa[N],rk[N],bac[N],y[N],tmp[N];//sa[i]数组表示后缀排名为i的位置,rk[i]表示后缀[i..n]的排名
    int height[N];//height[i]表示rk为i的后缀与rk为i-1的后缀的LCP
    void getsa(){
        //rk[i]表示位置i的第一关键字(排名)
        //二元组(rk[i],rk[i+k])
        //初始化基数排序(单字符,其实是单元)
        for(int i=1;i<=n;i++)bac[rk[i]=s[i]]++;//bac[i]表示第一关键字小于i的个数
        for(int i=2;i<=m;i++)bac[i]+=bac[i-1];//故就先逐个统计,再求前缀和,相当于用桶计数
        for(int i=n;i>=1;i--)sa[bac[rk[i]]--]=i;//在第二关键字有序的情况下,对位置按照第一关键字的bac数组,逐个附排名 
        for(int k=1;k<=n;k<<=1){
            num=0;//排名的数量,初始化为单个字符的ASCLL码上限,m=ascll('z')=122
            //y[i]表示第二关键字排名为i的二元组的第一关键字位置(i)
            for(int i=n-k+1;i<=n;i++)y[++num]=i;//没有第二关键字的排名最靠前
            for(int i=1;i<=n;i++)if(sa[i]>k)y[++num]=sa[i]-k;//利用已有的sa_(k/2)数组,rk靠前的且有第一关键字的,将第一关键字位置sa_(k/2)[i]-k放入y
            //在上一轮中,rk_k数组已经求好了,倍增求解,
            //接下来利用基数排序,求出sa_k
            for(int i=1;i<=m;i++)bac[i]=0;
            for(int i=1;i<=n;i++)bac[rk[i]]++;//bac[i]表示第一关键字小于i的个数
            for(int i=2;i<=m;i++)bac[i]+=bac[i-1];//故就先逐个统计,再求前缀和,相当于用桶计数
            for(int i=n;i>=1;i--)sa[bac[rk[y[i]]]--]=y[i];//在第二关键字有序的情况下,对位置按照第一关键字的bac数组,逐个附排名 
            memcpy(tmp,rk,sizeof(tmp));//用tmp存一下rk_(k/2),在求rk_k时仍会用到
            //其实就是利用sa_k和rk_(k/2)数组求rk_k
            rk[sa[1]]=1;num=1;
            for(int i=2;i<=n;i++){
                if(tmp[sa[i]]==tmp[sa[i-1]]&&tmp[sa[i]+k]==tmp[sa[i-1]+k])rk[sa[i]]=num;
                else rk[sa[i]]=++num;
            }
            if(num==n)break;//已经有了n种排名
            m=num;//m表示排名的数量(在桶排中为值域)
        }
    }
    void geth(){
        //rk[i]=rk1,sa[rk1-1]=x;
        //rk[i-1]=rk2,as[rk2-1]=y;
        //height[rk1]=lcp(s[i..],s[x...]);
        //height[rk2]=lcp[s[i-1..],s[y...]);
        //lcp(s[i..],s[x..])>=lcp(s[i-1...],s[y...])
        for(int i=1;i<=n;i++){
            int x=sa[rk[i]-1];
            int k=std::max(0,height[rk[i-1]]-1);
            while(s[i+k]==s[x+k])++k;
            height[rk[i]]=k;
        }
    }
    int main(){
        scanf("%s",s+1);n=strlen(s+1);m=122;
        getsa();for(int i=1;i<=n;i++)printf("%d ",sa[i]);
        printf("
    ");
        geth();for(int i=2;i<=n;i++)printf("%d ",height[i]);
        return 0;
    }
    

    SAM

    • SAM是一个最小的可以接受一个字符所有后缀的自动机
    • 构造的时候考虑用增量法,即每次放入一个字符
    • 构造完后,利用后缀连接link构造出link树(即parent树)
    • 即可得到所有的子串
    #include<bits/stdc++.h>
    #define fi first
    #define se second
    #define pb push_back
    #define mp make_pair
    #define SZ(x) ((int)x.size())
    #define ALL(x) x.begin(),x.end()
    #define U(i,u) for(register int i=head[u];i;i=nxt[i])
    #define rep(i,a,b) for(register int i=(a);i<=(b);++i)
    #define per(i,a,b) for(register int i=(a);i>=(b);--i)
    using namespace std;
    typedef long double ld;
    typedef long long ll;
    typedef unsigned int ui;
    typedef pair<int,int> PII;
    typedef vector<int> VI;
    template<class T> inline void read(T &x){
    	x=0; char c=getchar(); int f=1;
    	while (!isdigit(c)) {if (c=='-') f=-1; c=getchar();}
    	while (isdigit(c)) {x=x*10+c-'0'; c=getchar();} x*=f;
    }
    template<class T> inline void cmin(T &x, T y){x=x<y?x:y;}
    template<class T> inline void cmax(T &x, T y){x=x>y?x:y;}
    const int N=2000010;
    char s[N];
    int bac[N],a[N],cnt[N],n;
    ll ans;
    struct SAM{
    	int size,last;
    	int len[N<<1],link[N<<1];
    	int to[N<<1][30];
    	void extend(int c){
    		int p,cur=++size;
    		len[cur]=len[last]+1;
    		for(p=last;p&&!to[p][c];p=link[p])to[p][c]=cur;//情况1
    		if(!p)link[cur]=1;
    		else{
    			int q=to[p][c];
    			//2-A类
    			if(len[q]==len[p]+1)link[cur]=q;
    			//2-B类
    			else{
    				int cl=++size;
    				len[cl]=len[p]+1;
    				memcpy(to[cl],to[q],sizeof(to[cl]));link[cl]=link[q];
    				while(p&&to[p][c]==q){
    					to[p][c]=cl;
    					p=link[p];
    				}
    				link[cur]=link[q]=cl;
    			}
    		}
    		last=cur;
    		cnt[cur]=1;
    	}
    	void build(){
    		scanf("%s",s+1);last=size=1;n=strlen(s+1);
    		rep(i,1,n)extend(s[i]-'a');
    	}
    	void solve(){
    		rep(i,1,size)bac[len[i]]++;
    		rep(i,2,size)bac[i]+=bac[i-1];
    		rep(i,1,size)a[bac[len[i]]--]=i;//对后缀长度排序
    		per(i,size,1){
    			int p=a[i];//排名为i的状态为p
    			cnt[link[p]]+=cnt[p];
    			if(cnt[p]>1)cmax(ans,1ll*len[p]*cnt[p]);
    		}
    		printf("%lld
    ",ans);
    	}
    }sam;
    int main(){
    	sam.build();
    	sam.solve();
    	return 0;
    }
    

  • 相关阅读:
    【2】网站搭建:数据库选型
    【1】网站搭建:模型设计
    输入法评价
    站立会议个人博客六
    站立会议个人博客五
    站立会议个人博客四
    站立会议个人博客三
    第十一周学习进度表
    站立会议个人博客二
    典型用户和用户场景描述。
  • 原文地址:https://www.cnblogs.com/hangzz/p/13377950.html
Copyright © 2011-2022 走看看