1. 程式人生 > 實用技巧 >字串模板總結

字串模板總結

\(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\n",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\)素數的選擇:

可以參考,也可以選擇自己喜歡的質數。

\(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\n",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\text{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]];