字串模板總結
\(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\)陣列的意義:
- 失配後的下一個匹配位置。
- 字首的最長的\(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]];