• 字符串模板总结


    (I/O)

    读入一个字符:

    scanf("%c",c);
    cin>>c;
    c=getchar();
    

    读入一个字符串:

    scanf("%s",s);
    cin>>s;
    fgets(s,len,stdin);		//读入一行,不要用gets
    scanf("%s",s+1);		//下标从1开始
    

    输出一个字符:

    printf("%c",c);
    cout<<c;
    putchar(c);
    

    输出一个字符串:

    printf("%s",s);
    cout<<s;
    puts(s);		//等价于printf("%s
    ",s);
    

    (hash)

    把字符串有效地转化为一个整数。

    单哈希版:

    预处理(1)(n)的前缀(hash)值:

    for(int i=1;i<=n;++i)
    	ha[i]=(ha[i]*base+s[i])%mod;
    

    取子串的(hash)值:

    return (ha[r]-ha[l-1]*pw[r-l+1]+mod)%mod;
    

    双哈希版:

    预处理(1)(n)的前缀(hash)值:

    for(int i=1;i<=lena;i++)
    	for(int j=0;j<2;j++)
    		ha[i][j]=(ha[i-1][j]*base[j]+s[i])%mod[j];
    

    取子串的(hash)值:

    return make_pair((ha[r][0]-ha[l-1][0]*pw[r-l+1][0]+mod[0])%mod[0]
     				,(ha[r][1]-ha[l-1][1]*pw[r-l+1][1]+mod[1])%mod[1]);
    

    自然上溢哈希:用(unsigned int)(unsigned long long)

    (hash)素数的选择:

    image.png

    可以参考,也可以选择自己喜欢的质数。

    (Kmp)

    模板(下标从(0)开始):

    void Get_next()
    {
    	int i=0,j;
    	next[0]=j=-1;
    	while(i<len2)
    	{
    		if(j==-1||b[i]==b[j])
                next[++i]=++j;
    		else j=next[j];
    	}
    }
    
    void Kmp()
    {
    	int i=0,j=0;
    	while(i<len1)
    	{
    		if(j==-1||a[i]==b[j]) 
                ++i,++j;
    		else j=next[j];
    		if(j==len2)
            {
    			printf("%d
    ",i-len2+1);
    			j=next[j];
    		}
    	}
    }
    

    时间复杂度(Theta(|S_1|+|S_2|))

    (next)数组的意义:

    1. 失配后的下一个匹配位置。
    2. 前缀的最长的(border)

    (border):定义一个字符串(s)(border)(s)的一个(s)本身的子串(t),满足(t)既是(s)的前缀,又是(s)的后缀,即前缀后缀最大值。

    注:如果下标从一开始,用(next)表示(border)长度的时候要减一。

    应用:最小循环节:

    给定一个字符串,询问还需要添加几个字符可以构成一个由n个循环节组成的字符串

    可知我们应该先求出字符串的最小循环节的长度:假设字符串的长度为(len),那么最小的循环节就是(cir = len-next[len] ;) 如果有(len\%cir == 0),那么这个字符串就是已经是完美的字符串,不用添加任何字符;如果不是完美的那么需要添加的字符数就是(cir - (len-(len/cir)*cir))),相当于需要在最后一个循环节上面添加几个。

    扩展(Kmp)

    给定母串S,和子串T。

    定义(n=|S|,m=|T|,extend[i]=S[i..n])与T的最长公共前缀长度。请在线性的时间复杂度内,求出所有的(extend[1..n])

    (next)数组意义:(next[i])表示(T[i..m])(T)的最长公共前缀长度。

    参考代码(下标从0开始):

    void get_next()
    {
    	int a=0,p=0;
    	nxt[0]=lent;
    	for(int i=1;i<lent;i++)
    	{
    		if(i+nxt[i-a]<p) nxt[i]=nxt[i-a];
    		else
    		{
    			if(i>=p) p=i;
    			while(p<lent&&t[p]==t[p-i]) p++;
    			nxt[i]=p-i;
    			a=i;
    		}
    	}
    }
    
    void get_extend()
    {
    	int a=0,p=0;
    	for(int i=0;i<lens;i++)
    	{
    		if(i+nxt[i-a]<p) extend[i]=nxt[i-a];
    		else
    		{
    			if(i>=p) p=i;
    			while(p<lens&&s[p]==t[p-i]) p++;
    			extend[i]=p-i;
    			a=i;
    		}
    	}
    }
    

    时间复杂度(Theta(|S|+|T|))

    (Manacher)

    代码:

    string s,a;
    cin>>s;
    a="$~";
    int len=s.length();
    for(int i=0;i<len;i++)
        a+=s[i],a+="~";
    int len2=a.length();
    vector<int> p(len2+5,0);
    int maxr=0,pos=0;
    int ans=0;
    for(int i=1;i<len2;i++)
    {
        p[i]= i<maxr ? min(p[2*pos-i],maxr-i) : 1;
        while(a[i-p[i]]==a[i+p[i]]) p[i]++;
        if(p[i]+i>maxr) maxr=p[i]+i,pos=i;
        ans=max(ans,p[i]);
    }
    

    时间复杂度(Theta(n))

    (Trie)

    模板代码(下标从一开始):

    void insert(char *a)		//插入
    {
    	int len=strlen(a),u=1;
    	for(int i=0;i<len;i++)
    	{
    		int c=a[i]-'a';
    		if(!tr[u][c]) tr[u][c]=++tot;
    		u=tr[u][c];++siz[u];		//siz表示子树中有几个串
    	}
    	book[u]=1;
        ++word[u];			//word表示当前点有几个字符串
    }
    
    int find(char *a)		//查询a是否存在
    {
    	int len=strlen(a),u=1;
    	for(int i=0;i<len;i++)
    	{
    		int c=a[i]-'a';
    		if(!tr[u][c]) return 0;
    		u=tr[u][c];
    	}
    	if(!book[u] return 0;
    	return 1;
    }
    
    void query(int u,int k)	//查询字典序第k大,存到s数组中
    {
        if(word[u]>=k) return;
        k-=word[u];
        for(int c=0;c<26;++c)
            if(tr[u][c])
            {
                if(k<=siz[tr[u][c]])
                    return s[++top]=c+'a',query(tr[u][c],k),void();
                else k-=siz[tr[u][c]];
            }
    }
    
    

    时间复杂度(Theta(sum|S|))

    (01 ext{Trie})处理异或

    void insert(int x)		//插入
    {
        int u=1;		//注意根节点没有记录siz
        for(int i=lim;~i;--i)
        {
            int s=x>>i&1;
            if(!tr[u][s]) tr[u][s]=++cnt;
            u=tr[u][s];++siz[u];
        }
    }
    
    int query(int u,int v,int x)		//找异或最大值
    {
        int res=0;
        for(int i=lim;~i;--i)
        {
            int s=x>>i&1;
            if(tr[u][s^1]) res|=(1<<i),u=tr[u][s^1];
            else u=tr[u][s];
        }
        return res;
    }
    

    可持久化版本

    void insert(int &now,int v,char *s)			//插入
    {
        now=++cnt;int u=now,len=strlen(s+1);
        memcpy(tr[u],tr[v],sizeof(tr[v]));
        for(int i=1;i<=len;++i)
        {
            int c=s[i]-'a';
            tr[u][c]=++cnt;u=tr[u][c];v=tr[v][c];
            siz[u]=siz[v]+1;word[u]=word[v];
            memcpy(tr[u],tr[v],sizeof(tr[v]));
        }
        ++word[u];
    }
    //其他操作与普通版本几乎无区别
    

    (AC)自动机

    计算(fail)指针:

    void Getfail()			//fail指针
    {
    	queue<int> que;
    	for(int i=0;i<26;i++)
    	  if(tr[0][i]) que.push(tr[0][i]);
    	while(!que.empty())
    	{
    		int u=que.front();que.pop();
    		for(int i=0;i<26;i++)
    		{
    			int &v=tr[u][i];
    			if(v) fail[v]=tr[fail[u]][i],que.push(v);
    			else v=tr[fail[u]][i];
    		}
    	}
    }
    

    模板(1)(下文的变量意义与(Trie)中的基本一样):

    给定(n)个模式串(s_i)和一个文本串(t),求有多少个不同的模式串在文本串里出现过。
    两个模式串不同当且仅当他们编号不同。

    代码:

    void query(string s)
    {
    	int u=0,ans=0,len=s.length();
    	for(int i=0;i<len;i++)
    	{
    		u=tr[u][s[i]-'a'];
    		for(int j=u;j&&word[j]!=-1;j=fail[j])
            {
    			ans+=word[j];
    			word[j]=-1;		//只找一遍
    		}
    	}
    	cout<<ans<<endl;
    }
    

    模板(2)

    (N)个由小写字母组成的模式串以及一个文本串(T)。每个模式串可能会在文本串中出现多次。你需要找出哪些模式串在文本串(T)中出现的次数最多。

    代码:

    void query(string s)
    {
    	int u=0,ans=-1,len=s.length();
    	for(int i=0;i<len;i++)
    	{
    		u=tr[u][s[i]-'a'];
    		for(int j=u;j;j=fail[j])
                vis[word[j]]++;		 //这里word的意义是该点对应串的编号
    	}
    	for(int i=1;i<=n;i++) ans=max(ans,vis[i]);
    	cout<<ans<<endl;
    	for(int i=1;i<=n;i++)、
            if(vis[i]==ans) cout<<ss[i]<<endl;
    }
    

    模板(3)

    给你一个文本串(S)(n)个模式串(T_{1..n}),请你分别求出每个模式串(T_i)(S)中出现的次数。

    数据不保证任意两个模式串不相同

    代码(拓扑排序):

    for(int i=0;i<len;i++)
    {
        int &v=tr[u][s[i]-'a'];
        u=v?v:v=++tot;
    }
    if(!idx[u]) idx[u]=id;		//在插入完后记一下每个点在原串中对应的id
    else fa[id]=idx[u];			//如果有一个点对应多个id,就像并查集一样连一个fa
    //记得fa要初始化为fa[i]=i
    
    int &v=tr[u][i];
    if(v) fail[v]=tr[fail[u]][i],que.push(v),++deg[fail[v]]; //在这里记录入度
    else v=tr[fail[u]][i];
    
    void query(string s)
    {
    	queue<int> que;
    	int len=s.length(),u=0;
    	for(int i=0;i<len;i++)
    		vis[u=tr[u][s[i]-'a']]++;
    	//在fail树上跑拓扑排序
    	for(int i=1;i<=tot;i++)
    	  if(!deg[i]) que.push(i);
    	while(!que.empty())
    	{
    		u=que.front();que.pop();
    		ans[idx[u]]=vis[u];
    		vis[fail[u]]+=vis[u];		//fail树上答案向上传递
    		deg[fail[u]]--;
    		if(!deg[fail[u]]) que.push(fail[u]);
    	}
    }
    

    后缀数组

    void rsort()
    {
        for(int i=0;i<=m;++i) tax[i]=0;
        for(int i=1;i<=n;++i) ++tax[rnk[i]];
        for(int i=1;i<=m;++i) tax[i]+=tax[i-1];
        for(int i=n;i>0;--i) sa[tax[rnk[tp[i]]]--]=tp[i];
    }
    
    void ssort()
    {
        rsort();
        for(int w=1,p;p<n;m=p,w<<=1)
        {
            p=0;
            for(int i=1;i<=w;++i) tp[++p]=n-w+i;
            for(int i=1;i<=n;++i) if(sa[i]>w) tp[++p]=sa[i]-w;
            rsort();
            swap(tp,rnk);
            rnk[sa[1]]=p=1;
            for(int i=2;i<=n;++i)
                rnk[sa[i]]=tp[sa[i]]==tp[sa[i-1]]&&tp[sa[i]+w]==tp[sa[i-1]+w]?p:++p;
        }
    }
    
    
    void Get_height()			//height[i]=lcp(i,i-1),两个后缀的lcp为一段区间height的rmq
    {
        int p=0,j;
        for(int i=1;i<=n;++i)
        {
            if(p) --p;
            j=sa[rnk[i]-1];
            while(a[i+p]==a[j+p]) ++p;
            height[rnk[i]]=p;
        }
    }
    

    后缀自动机

    (SAM)模板

    class SAM
    {
    private:
        int link[maxn<<1],tr[maxn<<1][26];
        int maxlen[maxn<<1],siz[maxn<<1],a[maxn<<1],las=1,cnt=1;
    public:
        void insert(int c)
        {
            int u=las,nu=las=++cnt;
            siz[nu]=1;maxlen[nu]=maxlen[u]+1;
            for(;u&&!tr[u][c];u=link[u]) tr[u][c]=nu;
            if(!u) return link[nu]=1,void();
            int v=tr[u][c];
            if(maxlen[v]==maxlen[u]+1) return link[nu]=v,void();
            int nv=++cnt;
            maxlen[nv]=maxlen[u]+1;link[nv]=link[v];link[v]=link[nu]=nv;
            memcpy(tr[nv],tr[v],sizeof(tr[v]));
            for(;u&&tr[u][c]==v;u=link[u]) tr[u][c]=nv;
        }
        void rsort(int x)				//通常需要一遍基数排序求拓扑序
        {
            memset(tax,0,sizeof(tax));
            for(int i=1;i<=cnt;++i) ++tax[maxlen[i]];
            for(int i=1;i<=x;++i) tax[i]+=tax[i-1];
            for(int i=cnt;i;--i) a[tax[maxlen[i]]--]=i;
        }
    };
    

    广义(SAM)模板(在线版)(每插入一个串前把(las)设为一):

    int insert(int c,int u)
    {
        if(tr[u][c])
        {
            int v=tr[u][c];
            if(maxlen[u]+1==maxlen[v]) return v;
            int nv=++scnt;
            maxlen[nv]=maxlen[u]+1;link[nv]=link[v];link[v]=nv;
            memcpy(tr[nv],tr[v],sizeof(tr[v]));
            for(;u&&tr[u][c]==v;u=link[u]) tr[u][c]=nv;
            return nv;
        }
        int nu=++scnt;
        maxlen[nu]=maxlen[u]+1;
        for(;u&&!tr[u][c];u=link[u]) tr[u][c]=nu;
        if(!u) return link[nu]=1,nu;
        int v=tr[u][c];
        if(maxlen[u]+1==maxlen[v]) return link[nu]=v,nu;
        int nv=++scnt;
        maxlen[nv]=maxlen[u]+1;link[nv]=link[v];link[v]=link[nu]=nv;
        memcpy(tr[nv],tr[v],sizeof(tr[v]));
        for(;u&&tr[u][c]==v;u=link[u]) tr[u][c]=nv;
        return nu;
    }
    
    

    广义(SAM)模板(离线版):

    struct Trie
    {
        int tr[maxn][26],cnt=1;
        void insert(char *s)
        {
            int len=strlen(s+1),u=1;
            for(int i=1;i<=len;++i)
            {
                int c=s[i]-'a';
                u=tr[u][c]?tr[u][c]:tr[u][c]=++cnt;
            }
        }
    }tt;
    
    int insert(int c,int u)
    {
        int nu=++cnt;
        maxlen[nu]=maxlen[u]+1;
        for(;u&&!tr[u][c];u=link[u]) tr[u][c]=nu;
        if(!u) return link[nu]=1,nu;
        int v=tr[u][c];
        if(maxlen[v]==maxlen[u]+1) return link[nu]=v,nu;
        int nv=++cnt;
        maxlen[nv]=maxlen[u]+1;link[nv]=link[v];link[v]=link[nu]=nv;
        memcpy(tr[nv],tr[v],sizeof(tr[v]));
        for(;u&&tr[u][c]==v;u=link[u]) tr[u][c]=nv;
        return nu;
    }
    
    void bfs()			//bfs建树
    {
        pos[1]=1;
        que.push(1);
        while(!que.empty())
        {
            int u=que.front();que.pop();
            for(int i=0;i<26;++i)
                if(tt.tr[u][i])
                    pos[tt.tr[u][i]]=insert(ipos[u]),que.push(tt.tr[u][i]);
        }
    }
    

    应用:

    求多个字符串的本质不同子串个数。

    答案为:(sum maxlen[i]-maxlen[link[i]])

    计算每个节点的(endpos)大小

    注意上文插入的时候记录的(siz),基数排序后把(siz)(parent)树上累加,最后每个点的(siz)即为(endpos)的大小。

    for(int i=cnt;i;--i) siz[link[a[i]]]+=siz[a[i]];
    
  • 相关阅读:
    Java规约之方法设计
    JVM第一篇 JVM与Java体系结构
    初学者学习Java方法集
    初学者学习Java方法集
    1.Spring-Boot 静态文件和页默认放置位置
    2.Spring-Boot常用配置解释
    3.Spring-Boot核心@SpringBootApplication介绍
    4.Spring-Boot中基于Junit的单元测试
    vue学习笔记(一) ---- vue指令(v-for 和 key 属性)
    vue学习笔记(一) ----- vue指令(菜单列表案例)
  • 原文地址:https://www.cnblogs.com/gxm123/p/13891656.html
Copyright © 2020-2023  润新知