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;
    }
    

  • 相关阅读:
    UVALive 7509 Dome and Steles
    HDU 5884 Sort
    Gym 101194H Great Cells
    HDU 5451 Best Solver
    HDU 5883 The Best Path
    HDU 5875 Function
    卡特兰数
    UVa 11729 Commando War 突击战
    UVa 11292 The Dragon of Loowater 勇者斗恶龙
    Spark Scala Flink版本对应关系
  • 原文地址:https://www.cnblogs.com/hangzz/p/13377950.html
Copyright © 2011-2022 走看看