ZOJ3891 K-hash
出处: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;
}
HDU5343 MZL's Circle Zhou
出处: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专题并进行过总结,但时间久远差不多都忘了,正好借机重温一下。