【后缀自动机】ZOJ3891、HDU5343
出处:ZOJ Monthly, July 2015 - K
题意:给出一个整数K(<=32)和一个长度50000以内由十进制数构成的字符串S,要求统计S的不重复子串所表示的数对K取模的结果。
思路:在SAM上按节点拓扑序DP,dp[x][i]表示x节点处取模后结果为i的方案数。
当x节点处接受字符c可转移到y时,枚举i(0<=i<k),可以得到方程:dp[y][(i*10+c)%k] = sigma{ dp[x][i] | tans[x][c]=y }
#include <iostream> #include <iomanip> #include <cassert> #include <cmath> #include <cstdio> #include <cstdlib> #include <cstring> #include <string> #include <algorithm> #include <vector> #include <queue> #include <set> #include <map> #include <bitset> #include <limits> using namespace std; #define ll long long #define ld long double #define pii pair<int,int> #define x first #define y second #define sf scanf #define pf printf #define vec vector #define pb push_back #define mp make_pair #define sz(a) int(a.size()) #define all(a) a.begin(),a.end() #define clr(a,b) memset(a,b,sizeof(a)) #define bin_cnt(x) __builtin_popcount(x) #define rep(i,a,b) for(int i=a;i<=b;i++) #define rrep(i,b,a) for(int i=b;i>=a;i--) #define srep(sub,s) for(int sub=s&(s-1);sub;sub=(sub-1)&s) #define irep(i,a) for(__typeof(a.begin()) i=a.begin();i!=a.end();i++) #define irrep(i,a) for(__typeof(a.rbegin()) i=a.rbegin();i!=a.rend();i++) #define inf numeric_limits<int>::max() #define finf numeric_limits<double>::infinity() #define eps 1e-8 #pragma GCC optimize ("O3") #define o3 __attribute__((optimize("O3"))) #pragma comment(linker, "/STACK:32505856") template<class T> inline T sqr(T a) {return a*a;} template<class T> inline void gmin(T&a,T b) {if(a>b)a=b;} template<class T> inline void gmax(T&a,T b) {if(a<b)a=b;} inline int dcmp(const double &a) {return a>eps?1:(a<-eps?-1:0);} struct Initializer{Initializer(){ios::sync_with_stdio(false);cin.tie(0);cout.tie(0);}}initializer; int K; ll ans[32]; struct SAM { #define N 50005 int tot,last; int go[N<<1][10],pre[N<<1],step[N<<1]; int chr[N<<1],din[N<<1]; ll dp[N<<1][32]; inline void new_node(int s) { step[++tot]=s; pre[tot]=0; din[tot]=0; clr(go[tot],0); clr(dp[tot],0); } inline void build(char *s) { tot=0,last=1; new_node(0); chr[tot]=0; int n=strlen(s); rep(i,0,n-1) { new_node(step[last]+1); int c=s[i]-'0'; chr[tot]=c; int p=last,np=tot,q,nq; for (;p&&!go[p][c];p=pre[p]) go[p][c]=np; if (!p) pre[np]=1; else { q=go[p][c]; if (step[q]==step[p]+1) pre[np]=q; else { new_node(step[p]+1), nq=tot; chr[tot]=c; rep(j,0,9) go[nq][j]=go[q][j]; pre[nq]=pre[q]; pre[np]=pre[q]=nq; for (;p&&go[p][c]==q;p=pre[p]) go[p][c]=nq; } } last=np; } } void solve() { clr(ans,0); dp[1][0]=1; queue<int> q; rep(i,1,tot) rep(j,0,9) if (go[i][j]) din[go[i][j]]++; rep(i,1,tot) if (!din[i]) q.push(i); while (sz(q)) { int i=q.front(); rep(j,0,9) if (go[i][j]) { if (!--din[go[i][j]]) { q.push(go[i][j]); } if (i==1 && j==0) continue; rep(k,0,K-1) { dp[go[i][j]][(k*10+j)%K] += dp[i][k]; } } rep(k,0,K-1) ans[k]+=dp[i][k]; q.pop(); } } } sam; int main() { static char s[N]; while (~sf("%s%d",s,&K)) { sam.build(s); sam.solve(); ans[0]-=string(s).find('0')==string::npos; rep(i,0,K-1) pf("%lld%s",ans[i],i<K-1?" ":"\n"); } return 0; }
出处:2015 Multi-University Training Contest 5 - 1001
题意:依次给出长度90000以内的A、B两串,求将A的子串与B的子串按顺序拼接能得到的不重复的串的个数。
思路:A、B两串分别建两个SAM处理,先将B串翻转,利用SAM(B)求以字符c结尾(开头)的子串个数。
对于SAM(A)中节点x,若x不能接受字符c,则将B串中以c开头的子串“接”在其后,构成Asub+Bsub的形式。
详见官方题解 http://blog.sina.com.cn/s/blog_15139f1a10102vokk.html
#include <iostream> #include <iomanip> #include <cassert> #include <cmath> #include <cstdio> #include <cstdlib> #include <cstring> #include <string> #include <algorithm> #include <vector> #include <queue> #include <set> #include <map> #include <bitset> #include <limits> using namespace std; #define ll long long #define ld long double #define pii pair<int,int> #define x first #define y second #define sf scanf #define pf printf #define vec vector #define pb push_back #define mp make_pair #define sz(a) int(a.size()) #define all(a) a.begin(),a.end() #define clr(a,b) memset(a,b,sizeof(a)) #define bin_cnt(x) __builtin_popcount(x) #define rep(i,a,b) for(int i=a;i<=b;i++) #define rrep(i,b,a) for(int i=b;i>=a;i--) #define srep(sub,s) for(int sub=s&(s-1);sub;sub=(sub-1)&s) #define irep(i,a) for(__typeof(a.begin()) i=a.begin();i!=a.end();i++) #define irrep(i,a) for(__typeof(a.rbegin()) i=a.rbegin();i!=a.rend();i++) #define inf numeric_limits<int>::max() #define finf numeric_limits<double>::infinity() #define eps 1e-8 #pragma GCC optimize ("O3") #define o3 __attribute__((optimize("O3"))) #pragma comment(linker, "/STACK:32505856") template<class T> inline T sqr(T a) {return a*a;} template<class T> inline void gmin(T&a,T b) {if(a>b)a=b;} template<class T> inline void gmax(T&a,T b) {if(a<b)a=b;} inline int dcmp(const double &a) {return a>eps?1:(a<-eps?-1:0);} struct Initializer{Initializer(){ios::sync_with_stdio(false);cin.tie(0);cout.tie(0);}}initializer; struct SAM { #define N 90009 int tot,last; int go[N<<1][26],pre[N<<1],step[N<<1]; int chr[N<<1]; inline void new_node(int s) { step[++tot]=s; pre[tot]=0; clr(go[tot],0); } inline void build(char *s) { tot=0,last=1; new_node(0); int n=strlen(s); rep(i,0,n-1) { new_node(step[last]+1); int c=s[i]-'a'; chr[tot]=c; int p=last,np=tot,q,nq; for (;p&&!go[p][c];p=pre[p]) go[p][c]=np; if (!p) pre[np]=1; else { q=go[p][c]; if (step[q]==step[p]+1) pre[np]=q; else { new_node(step[p]+1), nq=tot; chr[tot]=c; rep(j,0,25) go[nq][j]=go[q][j]; pre[nq]=pre[q]; pre[np]=pre[q]=nq; for (;p&&go[p][c]==q;p=pre[p]) go[p][c]=nq; } } last=np; } } } sam; #define ull unsigned long long char a[N],b[N]; ull dp[26]; int main() { int T; sf("%d",&T); while (T--) { sf("%s%s",a,b); clr(dp,0); reverse(b,b+strlen(b)); sam.build(b); rep(i,1,sam.tot) { dp[sam.chr[i]]+=sam.step[i]-sam.step[sam.pre[i]]; } ull ans=0; sam.build(a); rep(i,1,sam.tot) { ans+=(ull)sam.step[i]-sam.step[sam.pre[i]]; rep(c,0,25) if (!sam.go[i][c]) ans+=(ull)(sam.step[i]-sam.step[sam.pre[i]])*dp[c]; } cout<<ans+1<<endl; } return 0; }
两道题均围绕不重复子串的数量展开询问,很容易联想到使用后缀自动机求解。
以前做过较长时间的SAM专题并进行过总结,但时间久远差不多都忘了,正好借机重温一下。