1 . 求不同子串的种类
2.长度为k的字符串的个数
3.计算所有子串的和(0-9表示)
4.给定模式串 s , n 个匹配串 str
求每个匹配串的循环同构能够匹配的子串总数
学习粗http://hihocoder.com/problemset
模板一 我主要用这个
#include <bits/stdc++.h> #define LL long long #define P pair<int, int> #define lowbit(x) (x & -x) #define mem(a, b) memset(a, b, sizeof(a)) #define rep(i, a, n) for (int i = a; i <= n; ++i) const int maxn = 1000005; #define mid ((l + r) >> 1) #define lc rt<<1 #define rc rt<<1|1 using namespace std; // __int128 read() { __int128 x = 0, f = 1; char c = getchar(); while (c < '0' || c > '9') { if (c == '-') f = -1; c = getchar(); } while (c >= '0' && c <= '9') { x = x * 10 + c - '0'; c = getchar(); } return x * f;} // void print(__int128 x) { if (x < 0) { putchar('-'); x = -x; } if (x > 9) print(x / 10); putchar(x % 10 + '0');} const LL mod = 1e9 + 7; int len; struct SAM{ int trans[maxn<<1][26], slink[maxn<<1], maxlen[maxn<<1]; // 用来求endpos int indegree[maxn<<1], endpos[maxn<<1], rank[maxn<<1], ans[maxn<<1]; // 计算所有子串的和(0-9表示) LL sum[maxn<<1]; int last, now, root; inline void newnode (int v) { maxlen[++now] = v; mem(trans[now],0); } inline void extend(int c) { newnode(maxlen[last] + 1); int p = last, np = now; // 更新trans while (p && !trans[p][c]) { trans[p][c] = np; p = slink[p]; } if (!p) slink[np] = root; else { int q = trans[p][c]; if (maxlen[p] + 1 != maxlen[q]) { // 将q点拆出nq,使得maxlen[p] + 1 == maxlen[q] newnode(maxlen[p] + 1); int nq = now; memcpy(trans[nq], trans[q], sizeof(trans[q])); slink[nq] = slink[q]; slink[q] = slink[np] = nq; while (p && trans[p][c] == q) { trans[p][c] = nq; p = slink[p]; } }else slink[np] = q; } last = np; // 初始状态为可接受状态 endpos[np] = 1; } inline void init() { root = last = now = 1; slink[root]=0; mem(trans[root],0); } // 计算所有子串的和(0-9表示) inline LL getSum() { // 拓扑排序 for (int i = 1; i <= now; ++i) indegree[ maxlen[i] ]++; for (int i = 1; i <= now; ++i) indegree[i] += indegree[i-1]; for (int i = 1; i <= now; ++i) rank[ indegree[ maxlen[i] ]-- ] = i; mem(endpos, 0); endpos[1] = 1; // 从根节点向后求有效的入度 for (int i = 1; i <= now; ++i) { int x = rank[i]; for (int j = 0; j < 10; ++j) { int nex = trans[x][j]; if (!nex) continue; endpos[nex] += endpos[x]; // 有效入度 LL num = (sum[x] * 10 + endpos[x] * j) % mod; sum[nex] = (sum[nex] + num) % mod; // 状态转移 } } LL ans = 0; for (int i = 2; i <= now; ++i) ans = (ans + sum[i]) % mod; return ans; } inline void getEndpos() { // topsort for (int i = 1; i <= now; ++i) indegree[ maxlen[i] ]++; // 统计相同度数的节点的个数 for (int i = 1; i <= now; ++i) indegree[i] += indegree[i-1]; // 统计度数小于等于 i 的节点的总数 for (int i = 1; i <= now; ++i) rank[ indegree[ maxlen[i] ]-- ] = i; // 为每个节点编号,节点度数越大编号越靠后 // 从下往上按照slik更新 for (int i = now; i >= 1; --i) { int x = rank[i]; endpos[slink[x]] += endpos[x]; } } // 求不同的子串种类 inline LL all () { LL ans = 0; for (int i = root+1; i <= now; ++i) { ans += maxlen[i] - maxlen[ slink[i] ]; } return ans; } // 长度为K的字符串有多种,求出现次数最多的次数 inline void get_Maxk() { getEndpos(); for (int i = 1; i <= now; ++i) { ans[maxlen[i]] = max(ans[maxlen[i]], endpos[i]); } for (int i = len; i >= 1; --i) ans[i] = max(ans[i], ans[i+1]); for (int i = 1; i <= len; ++i) //cout << ans[i] << endl; printf("%d ", ans[i]); } }sam; int main() { // int n;scanf("%d",&n); // string T; // // for(int i=0 ; i<n ; i++) // { // // string str; // cin>>str; // T+=str; // T+=":"; // } string T;cin>>T; sam.init(); len=T.size(); for(int i=0 ; i<len ; i++) sam.extend(T[i]-'a'); cout<<sam.all()<<endl; //- sam.all(); }
模板二
在这个模板里面没有记录minlen[st] , 但是可以间接得知 ; 比如 minlen[st] = maxlen[silnk[st]]+1
#include<bits/stdc++.h> using namespace std; const int MAXL=1000005; const int MAXS=26; #define ll long long struct SAM { int n=0, len, st; //st为起始点 //n状态总数 //maxlen表示每个状态对应的子串中长度最大值,minlen最小值 //trans是转移边,根节点是1,所以0代表不存在 //slink表示绿色的suffix link //col表示节点颜色,col=1代表是后缀节点,=0代表不是后缀的节点 //indeg表示对于只含有suffix link的图的度,统计endposamu用 //endposa表示该状态对应的子串们出现的结尾位置,amu表示endpos集合的大小 //ans表示某一长度的子串的个数 //sam中每个状态字符串长度是连续的,要求某个状态对应的子串个数只需maxlen-minlen+1即可 int maxlen[2*MAXL+10], minlen[2*MAXL+10], trans[2*MAXL+10][MAXS], slink[2*MAXL+10], col[2*MAXL+10], indeg[2*MAXL+10], endposamu[2*MAXL+10], ans[MAXL]; int new_state(int _maxlen, int _minlen, int* _trans, int _slink) { n++; maxlen[n]=_maxlen; minlen[n]=_minlen; for(int i=0; i<MAXS; i++) { if(_trans==NULL) trans[n][i]=0; else trans[n][i]=_trans[i]; } slink[n]=_slink; return n; } int add_char(char ch, int u) { int c=ch-'a'; int z=new_state(maxlen[u]+1, -1, NULL, 0); col[z]=1; int v=u; while(v!=0&&trans[v][c]==0) { trans[v][c]=z; v=slink[v]; } if(v==0)//最简单的情况,suffix-path(u->S)上都没有对应字符ch的转移 { minlen[z]=1; slink[z]=1; indeg[1]++; return z; } int x=trans[v][c]; if(maxlen[v]+1==maxlen[x])//较简单的情况,不用拆分x { minlen[z]=maxlen[x]+1; slink[z]=x; indeg[x]++; return z; } int y=new_state(maxlen[v]+1, -1, trans[x], slink[x]); //最复杂的情况,拆分x col[y]=0; minlen[x]=maxlen[y]+1; slink[x]=y; minlen[z]=maxlen[y]+1; slink[z]=y; indeg[y]+=2; int w=v; while(w!=0&&trans[w][c]==x) { trans[w][c]=y; w=slink[w]; } minlen[y]=maxlen[slink[y]]+1; return z; } void init() { memset(col, 0, sizeof(col)); memset(indeg, 0, sizeof(indeg)); memset(maxlen, 0, sizeof(maxlen)); memset(minlen, 0, sizeof(maxlen)); memset(trans, 0, sizeof(maxlen)); memset(slink, 0, sizeof(maxlen)); memset(endposamu, 0, sizeof(endposamu)); n=0;//多case也要清空n st=new_state(0, -1, NULL, 0); } void getendpos() { queue<int> que; for(int i=st;i<=n;i++) { if(indeg[i]==0) que.push(i); if(col[i]==1) endposamu[i]++; } while(!que.empty()) { int pos=que.front();que.pop(); endposamu[slink[pos]]+=endposamu[pos]; indeg[slink[pos]]--; if(indeg[slink[pos]]==0) que.push(slink[pos]); } } void addstring(string s) { int la=st; for(int i=0 ; i<s.size() ; i++) la=add_char(s[i],la); } ///求长度为为k的字符串有多少种 void klensum(string s) { /// ans[i] 得到的是长度为 i 不同字符串的个数 getendpos(); for(int i=st;i<=n;i++) { ans[maxlen[i]]=max(ans[maxlen[i]], endposamu[i]); } for(int i=s.length()-1;i>=1;i--) { ans[i]=max(ans[i], ans[i+1]); } for(int i=1 ; i<=s.size() ; i++) printf("%d ",ans[i]); } ///求不同子串的种类 // ans=∑maxlen−minlen void all() { ll sum=0; for(int i=st ; i<=n ; i++) { sum+=maxlen[i]-maxlen[slink[i]]; } printf("%lld ",sum); } }sam; int main() { sam.init(); string str; cin>>str; sam.addstring(str); sam.klensum(str); // sam.all(); }
代码的介绍:
#include<stdio.h> #include<bits/stdc++.h> #define maxc 28 using namespace std; const int maxn = 1e6 + 5; const int mod = 1e9 + 7; typedef long long ll; int len[maxn * 2], //最长子串的长度(该节点字串数量=len[x]-len[link[x]]) link[maxn * 2], //后缀链接(最短串前部减少一个字符所到达的状态) cnt[maxn * 2], //被后缀连接的数 nex[maxn * 2][maxc], //状态转移(尾部加一个字符的下一个状态)(图) idx, //节点编号 last; //最后节点 ll epos[maxn * 2]; // enpos数(该状态子串出现数量) char str[maxn]; ll a[maxn]; //长度为i的子串出现最大次数 void Iint() { //初始化 last = idx = 1; //1表示root起始点 空集 link[1] = len[1] = 0; } //SAM建图 void Extend(int c) { //插入字符,为字符ascll码值 int x = ++idx; //创建一个新节点x; len[x] = len[last] + 1; // 长度等于最后一个节点+1 epos[x] = 1; //接受节点子串除后缀连接还需加一 int p; //第一个有C转移的节点; for (p = last; p && !nex[p][c]; p = link[p])nex[p][c] = x;//沿着后缀连接 将所有没有字符c转移的节点直接指向新节点 if (!p)link[x] = 1, cnt[1]++; //全部都没有c的转移 直接将新节点后缀连接到起点 else { int q = nex[p][c]; //p通过c转移到的节点 if (len[p] + 1 == len[q]) //pq是连续的 link[x] = q, cnt[q]++; //将新节点后缀连接指向q即可,q节点的被后缀连接数+1 else { int nq = ++idx; //不连续 需要复制一份q节点 len[nq] = len[p] + 1; //令nq与p连续 link[nq] = link[q]; //因后面link[q]改变此处不加cnt memcpy(nex[nq], nex[q], sizeof(nex[q])); //复制q的信息给nq for (; p&&nex[p][c] == q; p = link[p]) nex[p][c] = nq; //沿着后缀连接 将所有通过c转移为q的改为nq link[q] = link[x] = nq; //将x和q后缀连接改为nq cnt[nq] += 2; // nq增加两个后缀连接 } } last = x; //更新最后处理的节点 } void GetNpos() { //求npos数,即该节点字串出现次数 queue<int>q; for (int i = 1; i <= idx; i++) if (!cnt[i])q.push(i); //将所有没被后缀连接指向的节点入队 while (!q.empty()) { int x = q.front(); q.pop(); epos[link[x]] += epos[x]; //子串数量等于所有后缀连接指向该节点的子串数量和+是否为接受节点 if (--cnt[link[x]] == 0)q.push(link[x]); //当所有后缀连接指向该节点的处理完毕后再入队 } } void GetSubNum() { //求不相同字串数量 ll ans = 0; for (int i = 2; i <= idx; i++)ans += len[i] - len[link[i]]; //一状态子串数量等于len[i]-len[link[i]] printf("%lld ",ans); } void GetSubMax() { //求出所有长度为k的子串中出现次数最多的子串出现次数 GetNpos(); int n = strlen(str); for (int i = 1; i <= idx; i++)a[len[i]] = max(a[len[i]], epos[i]); //长度≤k的子串中出现次数最多的子串出现次数的最小值 for (int i = n - 1; i >= 1; i--)a[i] = max(a[i], a[i + 1]); //求一遍后缀最大值就是答案 for (int i = 1; i <= n; i++)printf("%lld ", a[i]); } int main() { //freopen("c:/input.txt","r",stdin); return 0; }
计算所有子串的和(0-9表示)包括前导0
这里需要些字符串的链接技巧用“:”将每个字符串链接起来
#include <bits/stdc++.h> #define LL long long #define P pair<int, int> #define lowbit(x) (x & -x) #define mem(a, b) memset(a, b, sizeof(a)) #define rep(i, a, n) for (int i = a; i <= n; ++i) const int maxn = 1000005; #define mid ((l + r) >> 1) #define lc rt<<1 #define rc rt<<1|1 using namespace std; // __int128 read() { __int128 x = 0, f = 1; char c = getchar(); while (c < '0' || c > '9') { if (c == '-') f = -1; c = getchar(); } while (c >= '0' && c <= '9') { x = x * 10 + c - '0'; c = getchar(); } return x * f;} // void print(__int128 x) { if (x < 0) { putchar('-'); x = -x; } if (x > 9) print(x / 10); putchar(x % 10 + '0');} const LL mod = 1e9 + 7; struct SAM{ int trans[maxn<<1][26], slink[maxn<<1], maxlen[maxn<<1]; // 用来求endpos int indegree[maxn<<1], endpos[maxn<<1], rank[maxn<<1], ans[maxn<<1]; // 计算所有子串的和(0-9表示) LL sum[maxn<<1]; int last, now, root, len; inline void newnode (int v) { maxlen[++now] = v; } inline void extend(int c) { newnode(maxlen[last] + 1); int p = last, np = now; // 更新trans while (p && !trans[p][c]) { trans[p][c] = np; p = slink[p]; } if (!p) slink[np] = root; else { int q = trans[p][c]; if (maxlen[p] + 1 != maxlen[q]) { // 将q点拆出nq,使得maxlen[p] + 1 == maxlen[q] newnode(maxlen[p] + 1); int nq = now; memcpy(trans[nq], trans[q], sizeof(trans[q])); slink[nq] = slink[q]; slink[q] = slink[np] = nq; while (p && trans[p][c] == q) { trans[p][c] = nq; p = slink[p]; } }else slink[np] = q; } last = np; // 初始状态为可接受状态 endpos[np] = 1; } inline void build(string s) { // scanf("%s", s); len = s.size(); root = last = now = 1; for (int i = 0; i < len; ++i) extend(s[i] - '0'); // extend(s[i] - '1'); } // 计算所有子串的和(0-9表示) inline LL getSum() { // 拓扑排序 for (int i = 1; i <= now; ++i) indegree[ maxlen[i] ]++; for (int i = 1; i <= now; ++i) indegree[i] += indegree[i-1]; for (int i = 1; i <= now; ++i) rank[ indegree[ maxlen[i] ]-- ] = i; mem(endpos, 0); endpos[1] = 1; // 从根节点向后求有效的入度 for (int i = 1; i <= now; ++i) { int x = rank[i]; for (int j = 0; j < 10; ++j) { int nex = trans[x][j]; if (!nex) continue; endpos[nex] += endpos[x]; // 有效入度 LL num = (sum[x] * 10 + endpos[x] * j) % mod; sum[nex] = (sum[nex] + num) % mod; // 状态转移 } } LL ans = 0; for (int i = 2; i <= now; ++i) ans = (ans + sum[i]) % mod; return ans; } inline void getEndpos() { // topsort for (int i = 1; i <= now; ++i) indegree[ maxlen[i] ]++; // 统计相同度数的节点的个数 for (int i = 1; i <= now; ++i) indegree[i] += indegree[i-1]; // 统计度数小于等于 i 的节点的总数 for (int i = 1; i <= now; ++i) rank[ indegree[ maxlen[i] ]-- ] = i; // 为每个节点编号,节点度数越大编号越靠后 // 从下往上按照slik更新 for (int i = now; i >= 1; --i) { int x = rank[i]; endpos[slink[x]] += endpos[x]; } } // 求不同的子串种类 inline LL all () { LL ans = 0; for (int i = root+1; i <= now; ++i) { ans += maxlen[i] - maxlen[ slink[i] ]; } return ans; } // 长度为K的字符串有多种,求出现次数最多的次数 inline void get_Maxk() { getEndpos(); for (int i = 1; i <= now; ++i) { ans[maxlen[i]] = max(ans[maxlen[i]], endpos[i]); } for (int i = len; i >= 1; --i) ans[i] = max(ans[i], ans[i+1]); for (int i = 1; i <= len; ++i) //cout << ans[i] << endl; printf("%d ", ans[i]); } }sam; int main() { int n;scanf("%d",&n); string T; for(int i=0 ; i<n ; i++) { string str; cin>>str; T+=str; T+=":"; } // string T;cin>>T; sam.build(T); cout<<sam.getSum()<<endl; //- sam.all(); }
计算所以子串的和(0-9)不包括前导0
#include <bits/stdc++.h> #define P pair<int, int> #define lowbit(x) (x & -x) #define mem(a, b) memset(a, b, sizeof(a)) #define rep(i, a, n) for (int i = a; i <= n; ++i) const int maxn = 100005; #define mid ((l + r) >> 1) #define lc rt<<1 #define rc rt<<1|1 using namespace std; // __int128 read() { __int128 x = 0, f = 1; char c = getchar(); while (c < '0' || c > '9') { if (c == '-') f = -1; c = getchar(); } while (c >= '0' && c <= '9') { x = x * 10 + c - '0'; c = getchar(); } return x * f;} // void print(__int128 x) { if (x < 0) { putchar('-'); x = -x; } if (x > 9) print(x / 10); putchar(x % 10 + '0');} const int mod = 2012; struct SAM{ int trans[maxn<<1][26], slink[maxn<<1], maxlen[maxn<<1]; // 用来求endpos int indegree[maxn<<1], endpos[maxn<<1], rank[maxn<<1], ans[maxn<<1]; // 计算所有子串的和(0-9表示) int sum[maxn<<1]; int last, now, root, len; inline void newnode (int v) { maxlen[++now] = v; } inline void extend(int c) { newnode(maxlen[last] + 1); int p = last, np = now; // 更新trans while (p && !trans[p][c]) { trans[p][c] = np; p = slink[p]; } if (!p) slink[np] = root; else { int q = trans[p][c]; if (maxlen[p] + 1 != maxlen[q]) { // 将q点拆出nq,使得maxlen[p] + 1 == maxlen[q] newnode(maxlen[p] + 1); int nq = now; memcpy(trans[nq], trans[q], sizeof(trans[q])); slink[nq] = slink[q]; slink[q] = slink[np] = nq; while (p && trans[p][c] == q) { trans[p][c] = nq; p = slink[p]; } }else slink[np] = q; } last = np; // 初始状态为可接受状态 endpos[np] = 1; } // 计算所有子串的和(0-9表示) inline int getSum() { // 拓扑排序 for (int i = 1; i <= now; ++i) indegree[ maxlen[i] ]++; for (int i = 1; i <= now; ++i) indegree[i] += indegree[i-1]; for (int i = 1; i <= now; ++i) rank[ indegree[ maxlen[i] ]-- ] = i; mem(endpos, 0); endpos[1] = 1; // 从根节点向后求有效的入度 for (int i = 1; i <= now; ++i) { int x = rank[i]; for (int j = 0; j < 10; ++j) { int nex = trans[x][j]; if (!nex) continue; if(maxlen[x]==0&&j==0) continue; endpos[nex] += endpos[x]; // 有效入度 endpos[nex]%=mod; int num = (sum[x] * 10 + endpos[x] * j) % mod; sum[nex] = (sum[nex] + num) % mod; // 状态转移 } } int ans = 0; for (int i = 2; i <= now; ++i) ans = (ans + sum[i]) % mod; return ans; } inline void init() { mem(trans,0);mem(maxlen,0);mem(slink,0); mem(indegree,0);mem(sum,0); root=now=last=1; } }sam; char str[maxn]; int main() { int n; while(~scanf("%d",&n)) { sam.init(); for(int i=1 ; i<=n; i++) { // mem(str,0); scanf("%s",str); int len=strlen(str); //printf("%d ",len); for(int j=0 ; j<len ; j++) { sam.extend(str[j]-'0'); }sam.extend(10); }printf("%d ",sam.getSum()); } //- sam.aint(); }
给定模式串 s , n 个匹配串 str
求每个匹配串的循环同构能够匹配的子串总数
#include<bits/stdc++.h> using namespace std; const int N = 200010; int read() { int ans = 0 , flag = 1; char ch = getchar(); while(ch > '9' || ch < '0') {if(ch == '-') flag = -1; ch = getchar();} while(ch >= '0' && ch <= '9') {ans = ans * 10 + ch - '0'; ch = getchar();} return ans * flag; } char s[N]; struct SAM { int ch[N << 1][26] , fa[N << 1] , l[N << 1] , siz[N << 1]; int bac[N] , T[N << 1]; int vis[N << 1]; int cnt , last; void ins(int c) { int x = last , nx = ++ cnt; last = nx; l[nx] = l[x] + 1; siz[nx] = 1; for(; x && !ch[x][c] ; x = fa[x]) ch[x][c] = nx; if(!x) fa[nx] = 1; else { int y = ch[x][c]; if(l[y] == l[x] + 1) fa[nx] = y; else { int ny = ++ cnt; l[ny] = l[x] + 1; memcpy(ch[ny] , ch[y] , sizeof(ch[y])); fa[ny] = fa[y]; fa[y] = fa[nx] = ny; for(; x && ch[x][c] == y ; x = fa[x]) ch[x][c] = ny; } } } void insert(char *s) { cnt = last = 1; int len = strlen(s); for(int i = 0 ; i < len ; ++ i) ins(s[i] - 'a'); for(int i = 1 ; i <= cnt ; ++ i) ++ bac[l[i]]; for(int i = 1 ; i <= len ; ++ i) bac[i] += bac[i - 1]; for(int i = 1 ; i <= cnt ; ++ i) T[bac[l[i]] --] = i; for(int i = cnt ; i ; -- i) siz[fa[T[i]]] += siz[T[i]]; } void cal(char * s , int qwe) { int n = strlen(s) , c , m = n * 2 - 1; int x = 1 , lenth = 0 , ans = 0; for(int i = 0 , h = 0 ; i < n * 2 - 1 ; ++ i , ++ h) { if(h >= n) h -= n; c = s[h] - 'a'; while(x && !ch[x][c]) {x = fa[x]; lenth = l[x];} if(ch[x][c]) {x = ch[x][c]; ++ lenth;} else {x = 1; lenth = 0;} if(lenth > n) while(l[fa[x]] >= n) {x = fa[x]; lenth = l[x];} if(lenth >= n && vis[x] != qwe) {vis[x] = qwe; ans += siz[x];} } printf("%d " , ans); } }sam; int main() { scanf("%s" , s); sam.insert(s); int t = read(); for(int i = 1 ; i <= t ; ++ i) { scanf("%s" , s); sam.cal(s , i); } return 0; }