zoukankan      html  css  js  c++  java
  • 字符串算法总结

    字符串hash

    hash函数:BKERHash、APHash、DJBHash、JSHash等,一般用BKERHash

    例题  hdu 2648

    #include<iostream>
    #include<cstring>
    #include<cmath>
    #include<algorithm>
    #include<stack>
    #include<cstdio>
    #include<queue>
    #include<map>
    #include<vector>
    #include<set>
    using namespace std;
    const int maxn=10005;
    const int INF=0x3fffffff;
    typedef long long LL;
    //字符串hash的应用
    int n,m;
    struct node{
    	char name[40];
    	int price;
    };
    vector<node> q[maxn];  //用于解决冲突
    unsigned int BKDRHash(char *str){  //hash函数 
    	unsigned int seed=31,key=0;
    	while(*str){
    		key=key*seed+(*str++);
    	}
    	return key&0x7fffffff;
    } 
    
    int main(){
    	int p[maxn];
    	char s[35];
    	node t;
    	int mom,key,x,rank,len;
    	while(cin>>n){
    		for(int i=0;i<maxn;i++) q[i].clear();
    		for(int i=0;i<n;i++){
    			cin>>t.name;
    			key=BKDRHash(t.name)%maxn; //记得取余 
    			q[key].push_back(t);
    		}
    		cin>>m;
    		
    		while(m--){
    			rank=0;len=0;
    			for(int i=0;i<n;i++){
    				cin>>x>>s;
    				key=BKDRHash(s)%maxn;
    				for(int j=0;j<q[key].size();j++){
    					if(strcmp(q[key][j].name,s)==0){
    						q[key][j].price+=x;
    						if(strcmp(s,"memory")==0)  mom=q[key][j].price;
    						else p[len++]=q[key][j].price;
    						break;
    					}
    				}
    			}
    			for(int i=0;i<len;i++) if(p[i]>mom) rank++;
    			cout<<rank+1<<endl;
    		}
    	}
    return 0;
    }
    

    字典树Trie树

    时间复杂度:查找和插入单词的复杂度都是O(M),M是待插入/待查找单词的长度

    空间复杂度:公共前缀只存一次

    应用:

    (1)字符串检索

    (2)词频统计

    (3)字符串排序:插入的时候,在树的平级按顺序插入,建好之后先序遍历即可

    (4)前缀匹配

    例如: hdu 1251 统计难题 求以某个字符串 为前缀的单词数量

    #include<iostream>
    #include<cstring>
    #include<cmath>
    #include<algorithm>
    #include<stack>
    #include<cstdio>
    #include<queue>
    #include<map>
    #include<vector>
    #include<set>
    using namespace std;
    const int maxn=1010;
    const int INF=0x3fffffff;
    typedef long long LL;
    ///字典树
    //求以某个字符串 为前缀的单词数量
    int trie[1000010][26];  //用数组定义字典树,!!!存储下一个字符的位置
    int num[1000010]={0};  //单词数量
    int pos=1; //当前新分配的存储位置,其实就是所有出现的
    void inser(char str[]){
    	int p=0;
    	for(int i=0;str[i];i++){
    		int n=str[i]-'a';
    		if(trie[p][n]==0)
    			trie[p][n]=pos++;  //不存在就分配一个 
    		p=trie[p][n]; //向下延旭
    		num[p]++; //以此为前缀的单词数量++ 
    	}
    } 
    int fin(char str[]){
    	int p=0;
    	for(int i=0;str[i];i++){  //如果存在这个公共前缀,那么肯定能够遍历完 
    		int n=str[i]-'a';
    		if(trie[p][n]==0) return 0; //不存在
    		p=trie[p][n]; 
    		//cout<<p<<endl;
    	}
    	return num[p];
    }
    int main(){
    	char aa[11];
    	while(gets(aa)){
    		if(!strlen(aa)) break;
    		inser(aa);
    		
    	}
    	while(gets(aa)){
    		cout<<fin(aa)<<endl;
    	}
    return 0;
    }
    

    KMP算法

    单模匹配算法,复杂度为O(n+m),主要就是对模式串求出next数组,然后与文本串匹配,在匹配时,文本串不会回退,next就是模式串匹配失败是回退的地方,所以复杂度低

     例题: 剪花布条

    #include<iostream>
    #include<cstring>
    #include<cmath>
    #include<algorithm>
    #include<stack>
    #include<cstdio>
    #include<queue>
    #include<map>
    #include<vector>
    #include<set>
    using namespace std;
    const int maxn=1010;
    const int INF=0x3fffffff;
    typedef long long LL;
    //KMP模板题
    char str[maxn],pat[maxn];
    int cnt;
    int Next[maxn];
    void getnext(char *st,int len){
    	Next[0]=0;
    	Next[1]=0;  //初始化
    	for(int i=1;i<len;i++){
    		int j=Next[i];
    		while(j&&st[i]!=st[j]) j=Next[j];
    		Next[i+1]=(st[i]==st[j])?j+1:0;
    	}
    }
    void kmp(char *s,char *p){
    	int last=-1;  //为什么要这个,看例子就知道了,这个是最后一次得到一个完整的模式串的位置
    	int n=strlen(s);
    	int m=strlen(p);
    	getnext(p,m);
    	int j=0;
    	for(int i=0;i<n;i++){
    		while(j&&s[i]!=p[j]) j=Next[j];
    		if(s[i]==p[j]) j++;
    		if(j==m){
    			if(i-last>=m){
    				cnt++;
    				last=i;
    			}
    		}
    	} 
    }
    int main(){
    	while(~scanf("%s",str)){
    		if(str[0]=='#') break;
    		scanf("%s",pat);
    		cnt=0;
    		kmp(str,pat);
    		printf("%d
    ",cnt);
    	}
    return 0;
    }
    

    模板

    #include<iostream>
    #include<cstring>
    #include<cmath>
    #include<algorithm>
    #include<stack>
    #include<cstdio>
    #include<queue>
    #include<map>
    #include<vector>
    using namespace std;
    //KMP算法:匹配字符串,O(n+m)
    //next数组:含义是什么?next[i]就是所求最长相等前后缀的前缀最后一位的下标,用递推的方法来求
    const int maxn=1001;
    int next[maxn];
    string text,pattern; //文本串、模式串
    void getnext(string s,int len) {  //针对模式串的 
    	int j=-1;
    	next[0]=-1; //第一步:初始化
    	for(int i=1; i<len; i++) {
    		//一开始有一个关于模式串的大循环
    		while(j!=-1&&s[i]!=s[j+1]) j=next[j]; //j不断回退(j=-1或s[i]==s[j+1]
    		if(s[i]==s[j+1]) j++; //相等:长度加1
    		next[i]=j;
    	}
    }
    //KMP算法:令i指向text当前需要匹配的一位,令j指向pattern当前已经匹配了的一位(text[i]==pattern[j+1])
    //next[j]数组的意思是:如果第j+1为匹配失败是,应该回退的位置
    //判断pattern是不是text的子串
    bool KMP(string a,string b) {
    	int n=a.length(),m=b.length();
    	getnext(b,m); //计算匹配串的next数组
    	int j=-1; //初始化,表示当前没有任意一位被匹配
    	for(int i=0; i<n; i++) {	//匹配文本串的每一位
    		while(j!=-1&&a[i]!=b[j+1]) j=next[j];
    		if(a[i]==b[j+1]) j++;
    		if(j==m-1) return 1; //如果匹配到了模式串的最后一位:匹配成功
    		//就没有了
    	}
    	return 0;
    }
    //计算模式串出现的次数
    int KMP2(string a,string b) {
    	int n=a.length(),m=b.length();
    	getnext(b,m); //计算匹配串的next数组
    	int ans=0;
    	int j=-1; //初始化,表示当前没有任意一位被匹配
    	for(int i=0; i<n; i++) {	//匹配文本串的每一位
    		while(j!=-1&&a[i]!=b[j+1]) j=next[j];
    		if(a[i]==b[j+1]) j++;
    		if(j==m-1) {
    			ans++;    //次数加1;
    			j=next[j];  //让j回退到next[j]继续匹配
    		}
    		//就没有了
    	}
    	cout<<"the text has "<<ans<<" pattern "<<endl;
    }
    //KPM优化:将next数组换为 nextval[]数组,为了避免不必要的回退!! 
    //nextval数组的含义是第j+1位匹配失败了,第j位应该退回的最佳位置 
    //失去了next数组本身的含义,但是降低了时间复杂度 
    int nextval[maxn];
    void getnextval(string a,int len){
    	int j=-1;
    	nextval[0]=-1; //初始化
    	for(int i=1;i<len;i++){
    		while(j!=-1&&a[i]!=a[j+1]) j=nextval[j]; 
    	if(a[i]==a[j+1]) j++;
    	//next数组的情况:直接:next[i]=j; 
    	if(j==-1||a[i+1]!=a[j+1]) nextval[i]=j; //这是不需要回退的情况
    	else nextval[i]=nextval[j]; 
    }
    	//i+1不需要判断是否越界:不必要(思考p463) 
    }
    int main() {
    	cin>>text>>pattern;
    	if(KMP(text,pattern)==1) cout<<"YES"<<endl; 
    	else cout<<"NO"<<endl;
    	
    	return 0;
    }
    

      

    AC自动机

    多模匹配算法,在一个文本串中匹配查找多个子串,把所有的模式串P弄成字典树,复杂度为O(km+nm)

    #include <queue>
    #include <cstdlib>
    #include <cmath>
    #include <cstdio>
    #include <string>
    #include <cstring>
    #include <iostream>
    #include <algorithm>
    using namespace std;
    typedef long long ll;
    const int maxn =  2*1e6+9;
    
    int trie[maxn][26]; //字典树
    int cntword[maxn];  //记录该单词出现次数
    int fail[maxn];     //失败时的回溯指针
    int cnt = 0;
    
    void insertWords(string s){
        int root = 0;
        for(int i=0;i<s.size();i++){
            int next = s[i] - 'a';
            if(!trie[root][next])
                trie[root][next] = ++cnt;
            root = trie[root][next];
        }
        cntword[root]++;      //当前节点单词数+1
    }
    void getFail(){
        queue <int>q;
        for(int i=0;i<26;i++){      //将第二层所有出现了的字母扔进队列
            if(trie[0][i]){
                fail[trie[0][i]] = 0;
                q.push(trie[0][i]);
            }
        }
    
    //fail[now]    ->当前节点now的失败指针指向的地方
    ////tire[now][i] -> 下一个字母为i+'a'的节点的下标为tire[now][i]
        while(!q.empty()){
            int now = q.front();
            q.pop();
    
            for(int i=0;i<26;i++){      //查询26个字母
                if(trie[now][i]){
                    //如果有这个子节点为字母i+'a',则
    //让这个节点的失败指针指向(((他父亲节点)的失败指针所指向的那个节点)的下一个节点)
                    //有点绕,为了方便理解特意加了括号
    
                    fail[trie[now][i]] = trie[fail[now]][i];
                    q.push(trie[now][i]);
                }
                else//否则就让当前节点的这个子节点
                    //指向当前节点fail指针的这个子节点
                    trie[now][i] = trie[fail[now]][i];
            }
        }
    }
    
    
    int query(string s){
        int now = 0,ans = 0;
        for(int i=0;i<s.size();i++){    //遍历文本串
            now = trie[now][s[i]-'a'];  //从s[i]点开始寻找
            for(int j=now;j && cntword[j]!=-1;j=fail[j]){
                //一直向下寻找,直到匹配失败(失败指针指向根或者当前节点已找过).
                ans += cntword[j];
                cntword[j] = -1;    //将遍历国后的节点标记,防止重复计算
            }
        }
        return ans;
    }
    
    int main() {
        int n;
        string s;
        cin >> n;
        for(int i=0;i<n;i++){
            cin >> s ;
            insertWords(s);
        }
        fail[0] = 0;
        getFail();
        cin >> s ;
        cout << query(s) << endl;
        return 0;
    }
    

      

    后缀树和后缀数组

    讲解:https://blog.csdn.net/weixin_30790841/article/details/96620579?depth_1-utm_source=distribute.pc_relevant.none-task-blog-BlogCommendFromBaidu-1&utm_source=distribute.pc_relevant.none-task-blog-BlogCommendFromBaidu-1

    https://blog.csdn.net/yxuanwkeith/article/details/50636898

    里面的三个数组sa[],rak[],height[],其实应用方面主要使用的是height[]数组,加上根据题目来的check函数,二分答案,得到结果

    模板:

    #include<iostream>
    #include<cstring>
    #include<cmath>
    #include<algorithm>
    #include<stack>
    #include<cstdio>
    #include<queue>
    #include<map>
    #include<vector>
    #include<set>
    using namespace std;
    const int maxn=100005;
    const int INF=0x3fffffff;
    typedef long long LL;
    char ch[maxn],all[maxn];
    int sa[maxn],rank[maxn],height[maxn];
    int tax[maxn],tp[maxn],a[maxn],n,m;
    char str[maxn];
    //rank[i] 第i个后缀的排名; SA[i] 排名为i的后缀位置; Height[i] 排名为i的后缀与排名为(i-1)的后缀的LCP
    //tax[i] 计数排序辅助数组; tp[i] rank的辅助数组(计数排序中的第二关键字),与SA意义一样。
    //a为原串
    void inti(){
    	scanf("%s",str);
    	n=strlen(str);
    	for(int i=0;i<n;i++) a[i+1]=str[i]; 
    }
    void rsort(){
    	//rank第一关键字,tp第二关键字。
    	for(int i=0;i<=m;i++) tax[i]=0;
    	for(int i=1;i<=n;i++) tax[rank[tp[i]]]++;
    	for(int i=1;i<=m;i++) tax[i]+=tax[i-1];
    	for(int i=n;i>=1;i--) sa[tax[rank[tp[i]]]--]=tp[i];
    	// //确保满足第一关键字的同时,再满足第二关键字的要求
    	//基数排序,把新的二元组排序。
    }
    int cmp(int *f,int x,int y,int w){
    	//通过二元组两个下标的比较,确定两个子串是否相同
    	return f[x]==f[y]&&f[x+w]==f[y+w];
    }
    void suffix(){   //SA
    	for(int i=1;i<=n;i++){
    		rank[i]=a[i];
    		tp[i]=i;
    	}
    	m=127;
    	rsort();  //一开始是以单个字符为单位,所以(m = 127)
    	for(int w=1,p=1,i;p<n;w+=w,m=p){  //把子串长度翻倍,更新rank
    		 //w 当前一个子串的长度; m 当前离散后的排名种类数
            //当前的tp(第二关键字)可直接由上一次的SA的得到
            for(p=0,i=n-w+1;i<=n;i++) tp[++p]=i;
            ////长度越界,第二关键字为0
            for(i=1;i<=n;i++)  if(sa[i]>w) tp[++p]=sa[i]-w;
            //更新SA值,并用tp暂时存下上一轮的rank(用于cmp比较)
            rsort();
            swap(rank,tp);
            rank[sa[1]]=p=1;
            for(int i=2;i<=n;i++) rank[sa[i]]=cmp(tp,sa[i],sa[i-1],w)?p:++p;
    	}
    	for() 
    	//离散,把相等的字符串rank设为相同
    	//LCP:height数组
    	int k=0,j;
    	for(int i=1;i<=n;height[rank[i++]]=k)
    		for(k=k?k-1:k,j=sa[rank[i]-1];a[i+k]==a[j+k];++k);
    	
    }
    
    
    int main(){
    	inti();
    	suffix();
    	int ans=height[2];
    	for(int i=2;i<=n;i++) ans+=max(height[i]-height[i-1],0);
    	printf("%d
    ",ans);
    return 0;
    }
    

    这个也讲的比较好 https://www.sogou.com/link?url=hedJjaC291P3yGwc7N55kLSc2ls_Ks2xshTuaVXSLuaW4hUXSBiOy2Zklz_QowJGreaVHN88tHY.

    #include<cstdio>
    #include<cstring>
    using namespace std;
    /*
    rank[i]表示编号为i的排名 
    sa[i]表示排名为i的编号 
    cnt[i]计数排序的桶 
    pos[i]表示当前第二关键字已经排好序时第i名第二关键字所对应的第一关键字位置
    tmp[i]
    排序时:表示当前排序中编号为i的排名
    排序后:表示调整rank前的排名
    */
    char s[1200000];
    int cnt[1200000],pos[1200000],sa[1200000],tmp[1200000],rank[1200000];
    bool pd(int x,int y,int k){return tmp[x]==tmp[y]&&tmp[x+k]==tmp[y+k];}
    void suffix(int n,int m)
    {
    	int i,k;
    	for(i=1;i<=n;i++)rank[i]=s[i],cnt[rank[i]]++;
    	for(i=1;i<=m;i++)cnt[i]+=cnt[i-1];
    	for(i=n;i>=1;i--)sa[cnt[rank[i]]--]=i;//计数排序,设置好rank和sa数组 
    	for(k=1;k<n;k<<=1)//k表示长度,k<<1表示k*2 
    	{
    		int len=0;for(i=n-k+1;i<=n;i++)pos[++len]=i;//如果第二关键字为0的话,肯定最小,我们先加入pos 
    		for(i=1;i<=n;i++)if(sa[i]>k)pos[++len]=sa[i]-k;//准备好pos数组 
    		
    		memset(cnt,0,sizeof(cnt));
    		for(i=1;i<=n;i++)tmp[i]=rank[pos[i]],cnt[tmp[i]]++;
    		for(i=1;i<=m;i++)cnt[i]+=cnt[i-1];
    		for(i=n;i>=1;i--)sa[cnt[tmp[i]]--]=pos[i];//更新sa数组 
    		
    		for(i=1;i<=n;i++)tmp[i]=rank[i];//记录之前的排名 
    		
    		len=1;rank[sa[1]]=1;//初始化 
    		for(i=2;i<=n;i++){if(pd(sa[i],sa[i-1],k)==false)len++;rank[sa[i]]=len;}//通过sa来更新rank数组,并且去重 
    		if(len==n)break;m=len; 
    	}
    	for(i=1;i<n;i++)printf("%d ",sa[i]);
    	printf("%d
    ",sa[n]);
    }
    int main()
    {
    	scanf("%s",s+1);int len=strlen(s+1);
    	suffix(len,130);//'z'是122 ,为了保险设置为130
    	return 0;
    }

     例题来源   https://blog.csdn.net/qq_36038511/article/details/78133190

    POJ 1743 Musical Theme

    不可重叠最长重复子串
    题意:有N(1 <= N <=20000)个音符的序列来表示一首乐曲,每个音符都是1..88范围内的整数,现在要找一个重复的主题。“主题”是整个音符序列的一个子串,它需要满足如下条件:
    1.长度至少为5个音符。
    2.在乐曲中重复出现。(可能经过转调,“转调”的意思是主题序列中每个音符都被加上或减去了同一个整数值)
    3.重复出现的同一主题不能有公共部分。

    #include<iostream>
    #include<cstring>
    #include<cmath>
    #include<algorithm>
    #include<stack>
    #include<cstdio>
    #include<queue>
    #include<map>
    #include<vector>
    #include<set>
    using namespace std;
    const int maxn=21010;
    const int INF=0x3fffffff;
    typedef long long LL;
    /*
    caioj1467: 后缀数组1:不可重叠最长重复子串
    题意:有N(1 <= N <=20000)个音符的序列来表示一首乐曲,每个音符都是1..88范围内的整数,现在要找一个重复的主题。“主题”是整个音符序列的一个子串,它需要满足如下条件: 
    1.长度至少为5个音符。 
    2.在乐曲中重复出现。(可能经过转调,“转调”的意思是主题序列中每个音符都被加上或减去了同一个整数值) 
    3.重复出现的同一主题不能有公共部分。
    原文链接:https://blog.csdn.net/qq_36038511/article/details/78133190
    */ 
    int a[maxn],tt[maxn];
    char ss[maxn];
    int rak[maxn],sa1[maxn],sa2[maxn];
    int rsort[maxn];
    void get_sa(int n,int m){
    	memcpy(rak,a,sizeof(rak));
    	memset(rsort,0,sizeof(rsort));
    	for(int i=1;i<=n;i++) rsort[rak[i]]++;
    	for(int i=1;i<=m;i++) rsort[i]+=rsort[i-1];
    	for(int i=n;i>=1;i--) sa1[rsort[rak[i]]--]=i;
    	int ln=1,p=0;
    	while(p<n){
    		int k=0;
    		for(int i=n-ln+1;i<=n;i++) sa2[++k]=i;
    		for(int i=1;i<=n;i++) if(sa1[i]-ln>0) sa2[++k]=sa1[i]-ln;
    		//第二关键字排序 
    		memset(rsort,0,sizeof(rsort));
    		for(int i=1;i<=n;i++) rsort[rak[i]]++;
    		for(int i=1;i<=m;i++) rsort[i]+=rsort[i-1];
    		for(int i=n;i>=1;i--) sa1[rsort[rak[sa2[i]]]--]=sa2[i];
    		for(int i=1;i<=n;i++) tt[i]=rak[i];  //tt辅助数组 
    		p=1;
    		rak[sa1[1]]=1;
    		for(int i=2;i<=n;i++){
    			if(tt[sa1[i]]!=tt[sa1[i-1]]||tt[sa1[i]+ln]!=tt[sa1[i-1]+ln]) p++;
    			rak[sa1[i]]=p;
    		}
    		m=p;
    		ln*=2;
    	}
    } 
    //height[i]:sa[i]和sa[i-1]的最长公共前缀的长度
        /*
        定义h[i]=height[rank[i]],
        h数组有以下性质: h[i]≥h[i-1]-1
     证明:
     (suffix:后缀)
    设suffix(k)是排在suffix(i-1)前一名的后缀,则它们的最长公共前缀是h[i-1]。那么同时++,suffix(k+1)将排在suffix(i)的前面(这里一定h[i-1]>1,如果h[i-1]≤1,上面的原式显然成立)并且suffix(k+1)和suffix(i)的最长公共前缀是h[i-1]-1(因为同时往后挪了一位,故-1),所以suffix(i)和在它前一名的后缀的最长公共前缀至少是h[i-1]-1。因此得证。
       实现的时候其实没有必要保存h数组,只须按照h[1],h[2],……,h[n]的顺序计算即可。
        */
    int height[maxn*10];
    void get_he(int n){ //主要是这个问题
    	 int j,k=0;
    	 for(int i=2;i<=n;i++){
    	 	j=sa1[rak[i]-1];  //前一位 
    	 	if(k!=0) k--;  //保证>0
    	    while(a[j+k]==a[i+k]) k++;  //暴力询问
    		height[rak[i]]=k; 
    	 }
    }
    bool check(int k,int n){  //检查有没有重叠 
    	for(int i=2;i<=n;i++){
    		if(height[i]>=k){
    			for(int j=i-1;j>=2;j--){
    				if(abs(sa1[i]-sa1[j])>=k) return 1;
    				if(height[j]<k) break;
    			}
    		}
    	}
    	return false;
    }
    int main(){
    	int n;
    	while(scanf("%d",&n)!=EOF){
    		if(n==0) break;
    		for(int i=1;i<=n;i++) scanf("%d",&a[i]);
    		int mmax=-9999999;
    		for(int i=1;i<n;i++){
    			a[i]=a[i+1]-a[i]+88;
    			if(mmax<a[i]) mmax=a[i];
    		}
    		a[n]=0;
    		n--;
    		get_sa(n,mmax);
    		get_he(n);
    		int l=1,r=n,ans=1;
    		while(l<=r){  //二分答案 
    			int mid=(l+r)/2;
    			if(check(mid,n)==true){
    				ans=mid;
    				l=mid+1;
    			}
    			else r=mid-1;
    		}
    		if(ans<4) printf("0
    ");
    		else printf("%d
    ",ans+1);
    	}
    return 0;
    }
    

    后缀数组2:可重叠的k次最长重复子串
    【问题描述】
    农夫John发现他的奶牛产奶的质量一直在变动。经过细致的调查,他发现:虽然他不能预见明天 产奶的质量,但连续的若干天的质量有很多重叠。我们称之为一个“模式”。 John的牛奶按质量可以被赋予一个0到1000000之间的数。并且John记录了N(1<=N<=20000)天的 牛奶质量值。他想知道最长的出现了至少K(2<=K<=N)次的模式的长度。 比如1 2 3 2 3 2 3 1 中 2 3 2 3出现了两次。当K=2时,这个长度为4。(可重叠的k次最长重复子串)

    #include<iostream>
    #include<cstring>
    #include<cmath>
    #include<algorithm>
    #include<stack>
    #include<cstdio>
    #include<queue>
    #include<map>
    #include<vector>
    #include<set>
    using namespace std;
    const int maxn=1100000;
    const int INF=0x3fffffff;
    typedef long long LL;
    /*
    caioj1468: 后缀数组2:可重叠的k次最长重复子串
    【问题描述】 
    农夫John发现他的奶牛产奶的质量一直在变动。经过细致的调查,他发现:虽然他不能预见明天 产奶的质量,但连续的若干天的质量有很多重叠。我们称之为一个“模式”。
     John的牛奶按质量可以被赋予一个0到1000000之间的数。并且John记录了N(1<=N<=20000)天的 牛奶质量值。他想知道最长的出现了至少K(2<=K<=N)次的模式的长度。 比
     如1 2 3 2 3 2 3 1 中 2 3 2 3出现了两次。当K=2时,这个长度为4。(可重叠的k次最长重复子串)
    
    */
    int tt[21000],a[21000],sa1[21000],sa2[21000],rak[21000];
    int rsort[maxn],height[21000];
    void get_sa(int n,int m){
    	memcpy(rak,a,sizeof(rak));
    	memset(rsort,0,sizeof(rsort));
    	for(int i=1;i<=n;i++) rsort[rak[i]]++;
    	for(int i=1;i<=m;i++) rsort[i]+=rsort[i-1];
    	for(int i=n;i>=1;i--) sa1[rsort[rak[i]]--]=i;
    	int ln=1,p=0;
    	while(p<n){
    		int k=0;
    		for(int i=n-ln+1;i<=n;i++) sa2[++k]=i;
    		for(int i=1;i<=n;i++) if(sa1[i]>ln) sa2[++k]=sa1[i]-ln;
    		memset(rsort,0,sizeof(rsort));
    		for(int i=1;i<=n;i++)  rsort[rak[i]]++;
    		for(int i=1;i<=m;i++)  rsort[i]+=rsort[i-1];
    		for(int i=n;i>=1;i--)  sa1[rsort[rak[sa2[i]]]--]=sa2[i];
    		memcpy(tt,rak,sizeof(rak));
    		p=1;
    		rak[sa1[1]]=1;
    		for(int i=2;i<=n;i++){
    			if(tt[sa1[i]]!=tt[sa1[i-1]]||tt[sa1[i]+ln]!=tt[sa1[i-1]+ln]) p++;
    			rak[sa1[i]]=p;
    		}
    		m=p;
    		ln*=2;
    	} 
    }
    void get_he(int n){
    	int j,k=0;
    	for(int i=1;i<=n;i++){
    		j=sa1[rak[i]-1];
    		if(k) k--;
    		while(a[i+k]==a[j+k]) k++;
    		height[rak[i]]=k;
    	}
    }
    int N,K;
    bool check(int x,int n){  //二分检查 
    	int tt=1;
    	for(int i=2;i<=n;i++){
    		if(height[i]>=x){
    			tt++;
    			if(tt==K) return true;
    		}
    		else tt=1;
    	}
    	return false;
    }
    
    int main(){
    	while(scanf("%d %d",&N,&K)!=EOF){
    		int maxx=0;
    		if(N==0) break;
    		for(int i=1;i<=N;i++){
    			scanf("%d",&a[i]);
    			maxx=max(maxx,a[i]);
    		}
    		get_sa(N,maxx);
    		get_he(N);
    		int l=1,r=N,ans=0;
    		while(l<=r){
    			int mid=(l+r)/2;
    			if(check(mid,N)) {
    				ans=mid;
    				l=mid+1;
    			}
    			else r=mid-1;
    		}
    		printf("%d
    ",ans);
    	}
    return 0;
    }

    caioj1469: 后缀数组3:连续重复子串

    【问题描述】 
    求两个字符串的最长公共子串。(长度不超过100000)

    把两个字符串接在一起,然后在中间插入一个从没有出现过的字符 
    注意判断找到的公共子串会不会在同一个字符串内

    #include<iostream>
    #include<cstring>
    #include<cmath>
    #include<algorithm>
    #include<stack>
    #include<cstdio>
    #include<queue>
    #include<map>
    #include<vector>
    #include<set>
    using namespace std;
    const int maxn=510000;
    const int INF=0x3fffffff;
    typedef long long LL;
    //求两个字符串的最长公共子串
    //把两个字符串接在一起,然后在中间插入一个从没有出现过的字符 
    //注意判断找到的公共子串会不会在同一个字符串内 
    int a[maxn],rak[maxn],rsort[maxn],sa1[maxn],sa2[maxn];
    char s1[210000],s2[210000];
    int tt[maxn],height[maxn];
    void get_sa(int n,int m){
    	for(int i=1;i<=n;i++) rak[i]=a[i];
    	memset(rsort,0,sizeof(rsort));
    	for(int i=1;i<=n;i++) rsort[rak[i]]++;
    	for(int i=1;i<=m;i++) rsort[i]+=rsort[i-1];
    	for(int i=n;i>=1;i--) sa1[rsort[rak[i]]--]=i;
    	
    	int p=0,ln=1;
    	while(p<n){
    		int k=0;
    		for(int i=n-ln+1;i<=n;i++) sa2[++k]=i;
    		for(int i=1;i<=n;i++) if(sa1[i]>ln) sa2[++k]=sa1[i]-ln;
    		
    		memset(rsort,0,sizeof(rsort));
    		for(int i=1;i<=n;i++) rsort[rak[i]]++;
    		for(int i=1;i<=m;i++) rsort[i]+=rsort[i-1];
    		for(int i=n;i>=1;i--) sa1[rsort[rak[sa2[i]]]--]=sa2[i];
    		
    		for(int i=1;i<=n;i++) tt[i]=rak[i];
    		p=1;rak[sa1[1]]=1;
    		for(int i=2;i<=n;i++){
    		if(tt[sa1[i]]!=tt[sa1[i-1]]||tt[sa1[i]+ln]!=tt[sa1[i-1]+ln]) p++;
    		rak[sa1[i]]=p;
    		}
    		m=p;
    		ln*=2;
    	}
    	a[0]=0;sa1[0]=0;
    }
    void get_he(int n){
    	int k=0;
    	for(int i=1;i<=n;i++){
    		int j=sa1[rak[i]-1];
    		if(k) k--;
    		while(a[i+k]==a[j+k]) k++;
    		height[rak[i]]=k;
    	}
    }
    int main(){
    	int n,maxx=0;
    	scanf("%s",s1+1);
    	int lena=strlen(s1+1);
    	scanf("%s",s2+1);
    	int lenb=strlen(s2+1);
    	for(int i=1;i<=lena;i++){
    		a[i]=s1[i];
    		if(maxx<a[i]) maxx=a[i];
    	}
    	a[lena+1]='$';
    	n=lena+lenb+1;
    	int pp=0;
    	for(int i=lena+2;i<=n;i++){
    		a[i]=s2[++pp];
    		if(maxx<a[i]) maxx=a[i];
    	}
    	get_sa(n,maxx);
    	get_he(n);
    	int ans=0;
    	//能更大,也能保证不在同一个串里面 
    	for(int i=2;i<=n;i++){
    		if(ans<height[i]&&((sa1[i]<=lena&&sa1[i-1]>lena+1)||(sa1[i]>lena+1&&sa1[i-1]<=lena))) ans=height[i];
    	}
    	printf("%d
    ",ans);
    return 0;
    }

    caioj1470: 后缀数组4:Life Forms
    【问题描述】
    求n个字符串(长度1000)的最长的一个子串,满足该子串在一半以上的字符串中出现过,并输出该子串,如果有多个子串满足要求,则按字典序输出所有的子串;

    把所有字符串都扔在一起用从未出现过的字符隔开,
    然后判断在所有字符串中都出现过的字符串有多少种,
    最后输出

    #include <cstdio>
    #include <cstring>
    #include <algorithm>
    using namespace std;
    int a[1110000],kinds[1110000],Rank[1110000],Rsort[111000],sa1[1110000],sa2[1110000],tt[1110000],height[1110000];
    char s[110000];
    void get_sa(int n,int m)
    {
        for(int i=1;i<=n;i++) Rank[i]=a[i];
        memset(Rsort,0,sizeof(Rsort));
        for(int i=1;i<=n;i++) Rsort[Rank[i]]++;
        for(int i=1;i<=m;i++) Rsort[i]+=Rsort[i-1];
        for(int i=n;i>=1;i--) sa1[Rsort[Rank[i]]--]=i;
    
        int p=0,ln=1;
        while(p<n)
        {
            int k=0;
            for(int i=n-ln+1;i<=n;i++) sa2[++k]=i;
            for(int i=1;i<=n;i++) if(sa1[i]-ln>0) sa2[++k]=sa1[i]-ln;
    
            memset(Rsort,0,sizeof(Rsort));
            for(int i=1;i<=n;i++) Rsort[Rank[i]]++;
            for(int i=1;i<=m;i++) Rsort[i]+=Rsort[i-1];
            for(int i=n;i>=1;i--) sa1[Rsort[Rank[sa2[i]]]--]=sa2[i];
    
            for(int i=1;i<=n;i++) tt[i]=Rank[i];
            p=1;Rank[sa1[1]]=1;
            for(int i=2;i<=n;i++)
            {
                if(tt[sa1[i]]!=tt[sa1[i-1]]||tt[sa1[i]+ln]!=tt[sa1[i-1]+ln]) p++;
                Rank[sa1[i]]=p;
            }
            m=p;ln*=2;
        }
        a[0]=0;sa1[0]=0;
    }
    void get_height(int n)
    {
        int k=0;
        for(int i=1;i<=n;i++)
        {
            int j=sa1[Rank[i]-1];
            if(k) k--;
            while(a[i+k]==a[j+k]) k++;
            height[Rank[i]]=k;
        }
    }
    int stlen=0,v[210],start[1110000];
    bool check(int k,int n,int nn)
    {
        int ks=0,kind=0,stl=0;
        for(int i=1;i<=n;i++)
        {
            if(height[i]<k) 
            {
                if(ks>nn/2)
                {
                    stl++;
                    start[stl]=sa1[i-1];
                }
                memset(v,0,sizeof(v));
                ks=0;
            }
            kind=kinds[sa1[i]];
            if(v[kind]==0&&kind>0)
            {
                v[kind]=1;ks++;
            }
        }
        if(ks>nn/2)
        {
            stlen++;
            start[stl]=sa1[n];
        }
        if(stl) {stlen=stl;return true;}
        return false;
    }
    int main()
    {
        int n;
        while(scanf("%d",&n)!=EOF)
        {
            stlen=0;
            if(n==0) break;
            int maxx=500,st=0;
            for(int i=1;i<=n;i++) 
            {
                scanf("%s",s+1);
                int len=strlen(s+1);
                for(int j=1;j<=len;j++)
                {
                    a[j+st]=s[j];
                    kinds[j+st]=i;
                }
                a[len+st+1]=i+400;
                kinds[len+st+1]=0;
                st+=len+1;
            }
            get_sa(st,maxx);
            get_height(st);
            int l=1,r=1100,mid,len=0;
            while(l<=r)
            {
                mid=(l+r)/2;
                if(check(mid,st,n))
                {
                    len=mid;l=mid+1;
                }
                else r=mid-1;
            }
            for(int i=1;i<=stlen;i++)
            {
                for(int j=1;j<=len;j++) printf("%c",a[j+start[i]-1]);
                printf("
    ");
            }
            if(stlen==0) printf("?
    ");
            printf("
    ");
        }
    }
    

      

    一些其他的应用

    Q1:一个串中两个串的最大公共前缀是多少?
    A1:这不就是Height吗?用rmq预处理,再O(1)查询。

    Q2:一个串中可重叠的重复最长子串是多长?
    A2:就是求任意两个后缀的最长公共前缀,而任意两个后缀的最长公共前缀都是Height 数组里某一段的最小值,那最长的就是Height中的最大值。

    Q3:一个串中不可重叠的重复最长子串是多长?
    A3:先二分答案,转化成判别式的问题比较好处理。假设当前需要判别长度为k是否符合要求,只需把排序后的后缀分成若干组,其中每组的后缀之间的Height 值都不小于k,再判断其中有没有不重复的后缀,具体就是看最大的SA值和最小的SA值相差超不超过k,有一组超过的话k就是合法答案。

    A4:一个字符串不相等的子串的个数是多少?
    Q4:每个子串一定是某个后缀的前缀,那么原问题等价于求所有后缀之间的不相同的前缀的个数。而且可以发现每一个后缀Suffix[SA[i]]的贡献是Len - SA[i] + 1,但是有子串算重复,重复的就是Heigh[i]个与前面相同的前缀,那么减去就可以了。最后,一个后缀Suffix[SA[i]]的贡献就是Len - SA[k] + 1 - Height[k]。
    对于后缀数组更多的应用这里就不详细阐述,经过思考后每个人都会发现它的一些不同的用途,它的功能也许比你想象中的更强大!

  • 相关阅读:
    C项目实践--贪吃蛇(2)
    Dos下同时执行多条命令简化操作
    C语言进入界面编程准备篇
    C项目实践--图书管理系统(4)
    C项目实践--图书管理系统(3)
    C项目实践--图书管理系统(1)
    C项目实践--图书管理系统(2)
    bzoj2302: [HAOI2011]Problem c
    bzoj3545: [ONTAK2010]Peaks
    loj#2537. 「PKUWC2018」Minimax
  • 原文地址:https://www.cnblogs.com/shirlybaby/p/12704593.html
Copyright © 2011-2022 走看看