算法介绍见:http://www.cnblogs.com/Sakits/p/8232402.html

广义SAM资料:https://www.cnblogs.com/phile/p/4511571.html

【例题】

  参考http://www.cnblogs.com/Candyouth/p/5368750.html

  1.洛谷P3804【模板】后缀自动机

   因为$Parent$树上的叶子节点有可能变成一个父亲节点,所以可能某个叶子节点$r_i$不存在,比如$aa$,$r_i=1$的就不存在,它从一个叶子节点被更新成了父亲节点。

   所以对于这题要统计子串个数,需要对每个$np$的贡献设为1,然后在$Parent$树上跑一遍,因为$np$初始都是叶子节点,即使他变成了父亲节点,还是有1的贡献,注意$nq$一开始就是父亲节点所以没有贡献。

#include<iostream>
#include<cstring>
#include<cstdlib>
#include<cstdio>
#include<algorithm>
#define ll long long
using namespace std;
const int maxn=, inf=1e9;
struct sam{int len, fa, trans[];}st[maxn];
struct poi{int too, pre;}e[maxn];
int n, tott, tot, now, root, cnt[maxn], size[maxn], last[maxn];
ll ans;
char s[maxn];
inline void read(int &k)
{
int f=; k=; char c=getchar();
while(c<'' || c>'') c=='-'&&(f=-), c=getchar();
while(c<='' && c>='') k=k*+c-'', c=getchar();
k*=f;
}
inline void add(int x, int y){e[++tot]=(poi){y, last[x]}; last[x]=tot;}
inline void extend(int ch)
{
int np=++tott, p=now; size[np]=;
st[np].len=st[p].len+; now=np;
while(p && !st[p].trans[ch]) st[p].trans[ch]=np, p=st[p].fa;
if(!p) st[np].fa=root;
else
{
int q=st[p].trans[ch];
if(st[p].len+==st[q].len) st[np].fa=q;
else
{
int nq=++tott;
st[nq]=st[q];
st[nq].len=st[p].len+;
st[np].fa=st[q].fa=nq;
while(p && st[p].trans[ch]==q) st[p].trans[ch]=nq, p=st[p].fa;
}
}
}
void dfs(int x)
{
for(int i=last[x], too;i;i=e[i].pre) dfs(too=e[i].too), size[x]+=size[too];
if(size[x]!=) ans=max(ans, 1ll*size[x]*st[x].len);
}
int main()
{
scanf("%s", s+);
n=strlen(s+); now=tott=root=;
for(int i=;i<=n;i++) extend(s[i]-'a');
for(int i=;i<=tott;i++) add(st[i].fa, i);
dfs(); printf("%lld\n", ans);
}

  2.codevs3160 最长公共子串

   求两个字符串$s_1$和$s_2$的最长公共子串。

   先对$s_1$建SAM,设指针$x=root$,然后对于$s_2$一位一位进行以下操作:

   若$trans(x, s_2[i])!=null$,那么$x=trans(x, s_2[i])$,且当前答案$cnt++$

   若$trans(x, s_2[i])==null$,那么$x$在$Parent$树向上找到第一个满足$trans(y, s_2[i])!=null$的,令$cnt=len(y)+1,x=trans(y,s_2)$。

   若$Parent$树上没有,则$cnt$清零并$x=root$。

   因为在$Parent$树上的父亲都是当前子串的后缀,并且$Right$集合不断扩大,直到找到第一个有$x$边的后缀就可以从它开始更新答案了。

   效率?因为最长公共子串的长度不超过$n$,而每次往$Parent$树上爬一格都会使这个答案-1,最多减去$n$次,所以是$O(n)$的。

#include<iostream>
#include<cstring>
#include<cstdlib>
#include<cstdio>
using namespace std;
const int maxn=, inf=1e9;
struct sam{int len, fa, trans[];}st[maxn];
int n, m, x, now, root, tott, cnt, ans;
char s1[maxn], s2[maxn];
inline void extend(int ch)
{
int np=++tott, p=now;
st[np].len=st[p].len+; now=np;
while(p && !st[p].trans[ch]) st[p].trans[ch]=np, p=st[p].fa;
if(!p) st[np].fa=root;
else
{
int q=st[p].trans[ch];
if(st[p].len+==st[q].len) st[np].fa=q;
else
{
int nq=++tott;
st[nq]=st[q];
st[nq].len=st[p].len+;
st[np].fa=st[q].fa=nq;
while(p && st[p].trans[ch]==q) st[p].trans[ch]=nq, p=st[p].fa;
}
}
}
int main()
{
scanf("%s", s1+); scanf("%s", s2+);
n=strlen(s1+); m=strlen(s2+); root=tott=now=;
for(int i=;i<=n;i++) extend(s1[i]-'a');
x=root;
for(int i=;i<=m;i++)
{
if(st[x].trans[s2[i]-'a']) x=st[x].trans[s2[i]-'a'], cnt++;
else
{
while(x && !st[x].trans[s2[i]-'a']) x=st[x].fa;
if(!x) x=root, cnt=; else cnt=st[x].len+, x=st[x].trans[s2[i]-'a'];
}
ans=max(ans, cnt);
}
printf("%d\n", ans);
}

  3.SPOJ LCS2

   上一题的加强版,要求10个串的LCS。

   对一个串建SAM,其他串在上面跑,对于任意一个状态保存两种值,第一种保留同一个字符串匹配来的最大值,第二种保留这些最大值的最小值,最后答案就是每个状态最小值最大的那个。

   对于一个状态能匹配到,那么它的所有父亲也就是这个状态代表的子串的后缀也是能匹配到的,所以在跑完之后所有状态的最大值要对自己子树里的最大值取$max$,并且$min$一下本身的$max(s)$。

#include<iostream>
#include<cstring>
#include<cstdlib>
#include<cstdio>
#define ll long long
using namespace std;
const int maxn=, inf=1e9;
struct sam{int len, fa, trans[];}st[maxn];
int n, root, now, tott, ans;
int rk[maxn], cnt[maxn], mx[maxn], mn[maxn];
char s[maxn];
inline void extend(int ch)
{
int np=++tott, p=now;
st[np].len=st[p].len+; now=np;
while(p && !st[p].trans[ch]) st[p].trans[ch]=np, p=st[p].fa;
if(!p) st[np].fa=root;
else
{
int q=st[p].trans[ch];
if(st[p].len+==st[q].len) st[np].fa=q;
else
{
int nq=++tott;
st[nq]=st[q];
st[nq].len=st[p].len+;
st[np].fa=st[q].fa=nq;
while(p && st[p].trans[ch]==q) st[p].trans[ch]=nq, p=st[p].fa;
}
}
}
int main()
{
scanf("%s", s+); n=strlen(s+); root=now=tott=;
for(int i=;i<=n;i++) extend(s[i]-'a');
for(int i=;i<=tott;i++) mn[i]=st[i].len, cnt[st[i].len]++;
for(int i=;i<=n;i++) cnt[i]+=cnt[i-];
for(int i=tott;i;i--) rk[cnt[st[i].len]--]=i;
while(scanf("%s", s+)!=EOF)
{
memset(mx, , sizeof(mx));
n=strlen(s+); int len=, x=root;
for(int i=, ch;i<=n;i++)
if(st[x].trans[ch=s[i]-'a']) len++, x=st[x].trans[ch], mx[x]=max(mx[x], len);
else
{
while(x && !st[x].trans[ch]) x=st[x].fa;
if(!x) x=root, len=;
else len=st[x].len+, x=st[x].trans[ch], mx[x]=max(mx[x], len);
}
for(int i=tott, fa, x;i;i--)
{
x=rk[i]; fa=st[rk[i]].fa;
mx[fa]=max(mx[fa], min(mx[x], st[fa].len));
}
for(int i=;i<=tott;i++) mn[i]=min(mn[i], mx[i]);
}
for(int i=;i<=tott;i++) ans=max(ans, mn[i]);
printf("%d\n", ans);
}

  4.bzoj3238: [Ahoi2013]差异

   因为是要求最长公共前缀,所以我们需要把原串倒过来变成后缀。

   这样两个后缀的$LCP$,就是它们在$Parent$树上$LCA$的$len$。

   因为原串后缀的$LCP$,相当于新串后缀的最长公共后缀,而我们知道一个状态里的$Right$集合向左延伸$len$以内都是相等的,所以我们想要求原串两个后缀的$LCP$,只要知道它们的起点在新串中的位置$r_1,r_2$,并且找到$len$最大的$r_1,r_2$同时在$Right$集合里的状态就好了,而这个状态就是它们在$Parent$树上的$LCA$。

   于是树形DP一下就完了。

   一开始sb了调了好久...

#include<iostream>
#include<cstring>
#include<cstdlib>
#include<cstdio>
using namespace std;
const int maxn=, inf=1e9;
struct sam{int len, fa, trans[];}st[maxn];
int n, m, x, now, root, tott, cnt, ans;
char s1[maxn], s2[maxn];
inline void extend(int ch)
{
int np=++tott, p=now;
st[np].len=st[p].len+; now=np;
while(p && !st[p].trans[ch]) st[p].trans[ch]=np, p=st[p].fa;
if(!p) st[np].fa=root;
else
{
int q=st[p].trans[ch];
if(st[p].len+==st[q].len) st[np].fa=q;
else
{
int nq=++tott;
st[nq]=st[q];
st[nq].len=st[p].len+;
st[np].fa=st[q].fa=nq;
while(p && st[p].trans[ch]==q) st[p].trans[ch]=nq, p=st[p].fa;
}
}
}
int main()
{
scanf("%s", s1+); scanf("%s", s2+);
n=strlen(s1+); m=strlen(s2+); root=tott=now=;
for(int i=;i<=n;i++) extend(s1[i]-'a');
x=root;
for(int i=;i<=m;i++)
{
if(st[x].trans[s2[i]-'a']) x=st[x].trans[s2[i]-'a'], cnt++;
else
{
while(x && !st[x].trans[s2[i]-'a']) x=st[x].fa;
if(!x) x=root, cnt=; else cnt=st[x].len+, x=st[x].trans[s2[i]-'a'];
}
ans=max(ans, cnt);
}
printf("%d\n", ans);
}

  5.bzoj3998: [TJOI2015]弦论

   建出字符串的SAM,按字典序从小到大记忆化搜索即可,因为点数是$O(n)$的,所以效率$O(n)$。

#include<iostream>
#include<cstring>
#include<cstdlib>
#include<cstdio>
#define ll long long
using namespace std;
const int maxn=;
struct sam{int len, fa, trans[];}st[maxn];
int n, root, tott, now, T, K;
int cnt[maxn], rank[maxn], size[maxn], r[maxn];
ll ans, dp[maxn];
char s[maxn];
inline void extend(int x, int ch)
{
int np=++tott, p=now; size[np]=;
st[np].len=st[p].len+; now=np; r[np]=x;
while(p && !st[p].trans[ch]) st[p].trans[ch]=np, p=st[p].fa;
if(!p) st[np].fa=root;
else
{
int q=st[p].trans[ch];
if(st[p].len+==st[q].len) st[np].fa=q;
else
{
int nq=++tott;
st[nq]=st[q]; r[nq]=x;
st[nq].len=st[p].len+;
st[np].fa=st[q].fa=nq;
while(p && st[p].trans[ch]==q) st[p].trans[ch]=nq, p=st[p].fa;
}
}
}
inline void prepare()
{
for(int i=;i<=tott;i++) cnt[st[i].len]++;
for(int i=;i<=n;i++) cnt[i]+=cnt[i-];
for(int i=tott;i;i--) rank[cnt[st[i].len]--]=i;
for(int i=tott;i;i--) size[st[rank[i]].fa]+=size[rank[i]];
}
ll dfs(int x, int dep)
{
if(x!=)
{
if(dp[x] && K>dp[x]){K-=dp[x]; return dp[x];}
int num=T?size[x]:;
if(K<=num)
{
for(int i=r[x]-dep+;i<=r[x];i++) printf("%c", s[i]);
K=; return ;
}
if(K>num) K-=num, dp[x]+=num;
}
for(int i=;i<;i++)
{
if(size[st[x].trans[i]]) dp[x]+=dfs(st[x].trans[i], dep+);
if(!K) return ;
}
return dp[x];
}
int main()
{
scanf("%s", s+); n=strlen(s+); root=now=tott=;
for(int i=;i<=n;i++) extend(i, s[i]-'a');
prepare(); size[]=; scanf("%d%d", &T, &K);
dfs(, ); if(K) puts("-1");
}

  6.bzoj2555: SubString

   查一个子串出现了多少次,只要在SAM上跑出子串所在的状态,并求出这个状态$Right$集合的大小即可。

   因为要求在线,所以需要动态维护$Parent$树,这个用LCT显然可以做到,等以后会LCT了再补代码T T

   还有一种做法是用平衡树维护dfs序,对于一个点在平衡树上加入两个点,一个表示入栈序,一个表示出栈序,每次要把整个子树取出来的时候就把这两个点之间的所有点取出来就好了。为了取出来还需要一个求排名的函数,只要一直往父亲跑,途中更新一下排名就好了。

   但是平衡树因为操作比较多,点也是两倍的,加上SAM本身就是两倍大小的点,写的不够优美TLE了,懒得卡常,先留着吧。

#include<iostream>
#include<cstring>
#include<cstdlib>
#include<cstdio>
#include<algorithm>
#define ll long long
#define lt tree[x].ls
#define rt tree[x].rs
using namespace std;
const int maxn=, inf=(1LL<<)-;
struct sam{int len, fa, trans[];}st[maxn<<];
struct poi{int size, sum, fa, ls, rs, rnd, ans;}tree[maxn<<];
int n, root, tott, now, len, treaproot, mask, Q, ans;
char s[], ty[];
inline void read(int &k)
{
int f=; k=; char c=getchar();
while(c<'' || c>'') c=='-'&&(f=-), c=getchar();
while(c<='' && c>='') k=k*+c-'', c=getchar();
k*=f;
}
inline void up(int x)
{
tree[x].size=tree[lt].size+tree[rt].size+;
tree[x].sum=tree[lt].sum+tree[rt].sum+tree[x].ans;
tree[lt].fa=x; tree[rt].fa=x;
}
void split(int x, int &l, int &r, int k)
{
if(!k) l=, r=x;
else if(tree[x].size==k) l=x, r=;
else if(tree[lt].size>=k) r=x, split(lt, l, lt, k), up(x);
else l=x, split(rt, rt, r, k-tree[lt].size-), up(x);
}
void merge(int &x, int l, int r)
{
if(!l || !r) x=l+r;
else if(tree[l].rnd<tree[r].rnd) x=l, merge(rt, rt, r), up(x);
else x=r, merge(lt, l, lt), up(x);
}
inline int rank(int x)
{
int ans=tree[lt].size+;
while(x!=treaproot) ans+=(tree[tree[x].fa].rs==x?tree[tree[tree[x].fa].ls].size+:), x=tree[x].fa;
return ans;
}
inline void changefa(int pos, int fa)
{
int x, y, rkpos1=rank(pos), rkpos2=rank(pos+maxn);
split(treaproot, pos, y, rkpos2); split(pos, x, pos, rkpos1-);
merge(treaproot, x, y); int rk1=rank(fa);
split(treaproot, x, y, rk1); merge(x, x, pos); merge(treaproot, x, y);
}
inline void build(int x, int delta)
{
tree[x].rnd=(rand()<<)|rand();
tree[x+maxn].rnd=(rand()<<)|rand();
tree[x].size=tree[x+maxn].size=;
tree[x].sum=tree[x].ans=delta;
merge(x, x, x+maxn); merge(treaproot, treaproot, x);
}
inline void extend(int ch)
{
int np=++tott, p=now;
st[np].len=st[p].len+; now=np; build(np, );
while(p && !st[p].trans[ch]) st[p].trans[ch]=np, p=st[p].fa;
if(!p) st[np].fa=root, changefa(np, root);
else
{
int q=st[p].trans[ch];
if(st[p].len+==st[q].len) changefa(np, q), st[np].fa=q;
else
{
int nq=++tott;
build(nq, );
st[nq]=st[q]; changefa(nq, st[q].fa);
st[nq].len=st[p].len+;
st[np].fa=st[q].fa=nq;
changefa(np, nq); changefa(q, nq);
while(p && st[p].trans[ch]==q) st[p].trans[ch]=nq, p=st[p].fa;
}
}
}
inline int solve()
{
int p=root, ans=;
for(int i=;i<len;i++)
if(!st[p].trans[s[i]-'A']) return ;
else p=st[p].trans[s[i]-'A'];
int x, y, z, rk1=rank(p), rk2=rank(p+maxn);
split(treaproot, x, z, rk2); split(x, y, x, rk1-);
ans=tree[x].sum;
merge(x, x, z); merge(treaproot, y, x);
return ans;
}
inline void decodewithmask(int mask)
{
for(int i=;i<len;i++)
mask=(mask*+i)%len, swap(s[i], s[mask]);
}
int main()
{
read(Q); scanf("%s", s+);
n=strlen(s+); now=tott=root=;
tree[].rnd=inf; build(, );
for(int i=;i<=n;i++) extend(s[i]-'A');
while(Q--)
{
scanf("%s", ty+); scanf("%s", s);
len=strlen(s);
decodewithmask(mask);
if(ty[]=='Q') ans=solve(), mask^=ans, printf("%d\n", ans);
else for(int j=;j<len;j++) extend(s[j]-'A');
}
}

  7.bzoj3676: [Apio2014]回文串

   先把字符串的SAM跑出来,并记录每一个$r$的状态是哪一个,并预处理出每一个点在$Parent$的$2^i$倍祖先。

   因为考虑一个字符串,新加入一个字符最多只会生成一个新的本质不同的回文串,所以一个字符串中本质不同的回文串最多只有$n$个。那么我们可以先用manacher跑出那些能使$mx$改变的回文串,跑出来不一定是本质不同的,但一定不超过$n$个,这也就是manacher复杂度的证明了。

   每查到一个新的回文串,就找到右端点的位置的状态,然后在$Parent$上倍增找到第一个满足$len\geq$这个回文串长度的状态,并更新答案即可。

#include<iostream>
#include<cstring>
#include<cstdlib>
#include<cstdio>
#define ll long long
using namespace std;
const int maxn=, inf=1e9;
struct sam{int len, fa, trans[];}st[maxn];
int n, mid, mx, tott, root, now, last;
ll ans;
int p[maxn], fa[][maxn], pos[maxn], cnt[maxn], rank[maxn], size[maxn];
char s[maxn];
inline int max(int a, int b){return a>b?a:b;}
inline void extend(int x, int ch)
{
int np=++tott, p=now;
st[np].len=st[p].len+; now=np; size[np]=; pos[x]=np;
while(p && !st[p].trans[ch]) st[p].trans[ch]=np, p=st[p].fa;
if(!p) st[np].fa=root;
else
{
int q=st[p].trans[ch];
if(st[p].len+==st[q].len) st[np].fa=q;
else
{
int nq=++tott;
st[nq]=st[q];
st[nq].len=st[p].len+;
st[np].fa=st[q].fa=nq;
while(p && st[p].trans[ch]==q) st[p].trans[ch]=nq, p=st[p].fa;
}
}
}
inline void prepare()
{
for(int i=;i<=tott;i++) cnt[st[i].len]++;
for(int i=;i<=n;i++) cnt[i]+=cnt[i-];
for(int i=tott;i;i--) rank[cnt[st[i].len]--]=i;
for(int i=tott;i;i--) size[st[rank[i]].fa]+=size[rank[i]];
for(int i=;i<=tott;i++) fa[][i]=st[i].fa;
for(int i=;i<=;i++) for(int j=;j<=tott;j++) fa[i][j]=fa[i-][fa[i-][j]];
}
inline void query(int x, int len)
{
for(int i=;~i;i--)
if(st[fa[i][x]].len>=len) x=fa[i][x];
ans=max(ans, 1ll*size[x]*len);
}
int main()
{
scanf("%s", s+); n=strlen(s+); tott=root=now=last=;
for(int i=;i<=n;i++) extend(i, s[i]-'a');
prepare(); s[]='#'; s[n+]='$';
for(int i=;i<=n;i++)
{
if(mx>i) p[i]=min(p[(mid<<)-i], mx-i); else p[i]=, query(pos[i], );
while(s[i-p[i]]==s[i+p[i]]) p[i]++, query(pos[i+p[i]-], (p[i]<<)-);
if(p[i]+i>mx) mx=p[i]+i, mid=i;
}
mid=mx=;
for(int i=;i<=n;i++)
{
if(mx>i) p[i]=min(p[(mid<<)-i], mx-i); else p[i]=;
while(s[i-p[i]]==s[i+p[i]+]) p[i]++, query(pos[i+p[i]], p[i]<<);
if(p[i]+i>mx) mx=p[i]+i, mid=i;
}
printf("%lld\n", ans);
}

  8.bzoj4199: [Noi2015]品酒大会

   为什么NOI会出这种傻逼题...大概看了一眼就会写了

   首先求$LCP$肯定要先把串反过来,然后第一问就和bzoj差异那题一样了,第二问只需要记录一下子树的最大最小,次大次小就好了...

#include<iostream>
#include<cstring>
#include<cstdlib>
#include<cstdio>
#define ll long long
using namespace std;
const int maxn=, inf=1e9+;
const ll INF=5e18;
struct sam{int len, fa, trans[];}st[maxn];
struct poi{int too, pre;}e[maxn];
int n, tott, now, root, tot;
int mx[maxn], mx2[maxn], mn[maxn], mn2[maxn], val[maxn], size[maxn], last[maxn];
ll sum, cf[maxn], cnt[maxn], ans[maxn];
char s[maxn];
void read(int &k)
{
int f=; k=; char c=getchar();
while(c<'' || c>'') c=='-' && (f=-), c=getchar();
while(c<='' && c>='') k=k*+c-'', c=getchar();
k*=f;
}
inline void add(int x, int y){e[++tot]=(poi){y, last[x]}; last[x]=tot;}
inline void extend(int ch, int delta)
{
int np=++tott, p=now;
st[np].len=st[p].len+; now=np; size[np]=;
mn[np]=mx[np]=delta; mn2[np]=inf; mx2[np]=-inf;
while(p && !st[p].trans[ch]) st[p].trans[ch]=np, p=st[p].fa;
if(!p) st[np].fa=root;
else
{
int q=st[p].trans[ch];
if(st[p].len+==st[q].len) st[np].fa=q;
else
{
int nq=++tott;
mx[nq]=mx2[nq]=-inf;
mn[nq]=mn2[nq]=inf;
st[nq]=st[q];
st[nq].len=st[p].len+;
st[np].fa=st[q].fa=nq;
while(p && st[p].trans[ch]==q) st[p].trans[ch]=nq, p=st[p].fa;
}
}
}
inline void updatemx(int x, int delta)
{
if(delta>mx[x]) mx2[x]=mx[x], mx[x]=delta;
else if(delta>mx2[x]) mx2[x]=delta;
}
inline void updatemn(int x, int delta)
{
if(delta<mn[x]) mn2[x]=mn[x], mn[x]=delta;
else if(delta<mn2[x]) mn2[x]=delta;
}
void dfs(int x)
{
for(int i=last[x], too;i;i=e[i].pre)
{
dfs(too=e[i].too);
size[x]+=size[too];
updatemx(x, mx[too]); updatemx(x, mx2[too]);
updatemn(x, mn[too]); updatemn(x, mn2[too]);
}
if(size[x]<=) return; cf[st[st[x].fa].len+]+=1ll*size[x]*(size[x]-)>>;
cf[st[x].len+]-=1ll*size[x]*(size[x]-)>>;
ans[st[x].len]=max(ans[st[x].len], max(1ll*mx[x]*mx2[x], 1ll*mn[x]*mn2[x]));
}
int main()
{
read(n); scanf("%s", s+); root=now=tott=; st[].len=-;
mx[]=mx2[]=-inf; mn[]=mn2[]=inf;
for(int i=;i<=n;i++) read(val[i]);
for(int i=n;i;i--) extend(s[i]-'a', val[i]);
for(int i=;i<=tott;i++) add(st[i].fa, i);
for(int i=;i<=n;i++) ans[i]=-INF; dfs();
sum=; for(int i=;i<=n;i++) sum+=cf[i], cnt[i]=sum;
for(int i=n-;~i;i--) if(cnt[i+]) ans[i]=max(ans[i+], ans[i]);
for(int i=;i<n;i++) printf("%lld %lld\n", 1ll*cnt[i], ans[i]==-INF?:ans[i]);
}

  9.bzoj4310: 跳蚤

   好题。首先最大值最小化,二分第k小的子串为答案,上界为本质不同子串个数,即所有状态的$max(s)-min(s)+1$的和。

   求第k小的子串是上面的第4道例题,就不展开说了。

   显然一个区间里最大的子串一定是这个区间的一个后缀,所以我们check的时候倒着加字符,这样每次只会多一个后缀,然后用hash判一下当前字符是不是比第k小子串要大,如果是的话就断,否则继续找,就能求出最小的断的个数了。

#include<iostream>
#include<cstring>
#include<cstdlib>
#include<cstdio>
#define ll long long
#define ull unsigned long long
using namespace std;
const int maxn=;
struct sam{int len, fa, trans[];}st[maxn];
int n, root, tott, now, len, mxtim, p;
int cnt[maxn], rank[maxn], size[maxn], r[maxn];
ll K, ans, mx, dp[maxn];
ull mul[maxn], hs[][maxn];
char s[maxn], kths[maxn];
inline void extend(int x, int ch)
{
int np=++tott, p=now; size[np]=;
st[np].len=st[p].len+; now=np; r[np]=x;
while(p && !st[p].trans[ch]) st[p].trans[ch]=np, p=st[p].fa;
if(!p) st[np].fa=root;
else
{
int q=st[p].trans[ch];
if(st[p].len+==st[q].len) st[np].fa=q;
else
{
int nq=++tott;
st[nq]=st[q]; r[nq]=x;
st[nq].len=st[p].len+;
st[np].fa=st[q].fa=nq;
while(p && st[p].trans[ch]==q) st[p].trans[ch]=nq, p=st[p].fa;
}
}
}
inline void prepare()
{
for(int i=;i<=tott;i++) cnt[st[i].len]++;
for(int i=;i<=n;i++) cnt[i]+=cnt[i-];
for(int i=tott;i;i--) rank[cnt[st[i].len]--]=i;
for(int i=tott;i;i--) size[st[rank[i]].fa]+=size[rank[i]];
for(int i=;i<=tott;i++) mx+=st[i].len-st[st[i].fa].len;
size[]=;
}
ll dfs(int x, int dep)
{
ll ans=;
if(x!=)
{
if(dp[x] && K>dp[x]){K-=dp[x]; return dp[x];}
K--;
if(!K)
{
if(p) dp[x]=;
len=; for(int i=r[x]-dep+;i<=r[x];i++) kths[++len]=s[i];
return ;
}
ans++;
}
for(int i=;i<;i++)
{
if(size[st[x].trans[i]]) ans+=dfs(st[x].trans[i], dep+);
if(!K) return ;
}
if(p) dp[x]=ans;
return ans;
}
inline ull geths(int l, int r, int ty){return hs[ty][r]-hs[ty][l-]*mul[r-l+];}
inline bool cmp(int l, int r)
{
int L=, R=min(r-l+, len);
while(L<R)
{
int mid=(L+R+)>>;
if(geths(l, l+mid-, )==geths(, mid, )) L=mid;
else R=mid-;
}
if(geths(l, l+L-, )!=geths(, L, )) L--;
if(L==min(r-l+, len)) return r-l+>len;
return s[l+L]>kths[L+];
}
inline bool check()
{
for(int i=;i<=len;i++) hs[][i]=hs[][i-]*+kths[i]-'a'+;
int last=n, tim=;
for(int i=n;i;i--)
if(cmp(i, last))
{
tim++;
if(tim>mxtim) return ;
if(last==i) return ;
last=i; i++;
}
return ;
}
int main()
{
scanf("%d", &mxtim); mxtim--; scanf("%s", s+); n=strlen(s+); root=now=tott=;
for(int i=;i<=n;i++) hs[][i]=hs[][i-]*+s[i]-'a'+;
for(int i=;i<=n;i++) extend(i, s[i]-'a');
mul[]=; for(int i=;i<=n;i++) mul[i]=mul[i-]*;
prepare(); K=mx; p=; dfs(, ); p=;
ll l=, r=mx;
while(l<r)
{
ll mid=(l+r)>>;
K=mid; dfs(, );
if(check()) r=mid;
else l=mid+;
}
K=l; dfs(, );
for(int i=;i<=len;i++) printf("%c", kths[i]); puts("");
}

  10.bzoj4566: [Haoi2016]找相同字符

   很像例题2,公共子串大概都是这种写法。

   建出A串的SAM,B串在上面跑。

   能匹配就匹配,不能匹配就往父亲跳相当于看后缀能否匹配,找到第一个能匹配的地方后,把以当前位置为后端点的贡献计算一下,设一个状态$s$表示的子串的所有后缀的贡献是$sum(s)$,当前匹配长度为$nowlen$,这个贡献就是$sum(fa)+size(x)*(nowlen-len(fa))$。

#include<iostream>
#include<cstring>
#include<cstdlib>
#include<cstdio>
#define ll long long
using namespace std;
const int maxn=, inf=1e9;
struct sam{int fa, len, trans[];}st[maxn];
int n, m, now, root, tott;
int cnt[maxn], rank[maxn], size[maxn];
ll ans, sum[maxn];
char s1[maxn], s2[maxn];
inline void extend(int ch)
{
int np=++tott, p=now;
st[np].len=st[p].len+; now=np; size[np]=;
while(p && !st[p].trans[ch]) st[p].trans[ch]=np, p=st[p].fa;
if(!p) st[np].fa=root;
else
{
int q=st[p].trans[ch];
if(st[p].len+==st[q].len) st[np].fa=q;
else
{
int nq=++tott;
st[nq]=st[q];
st[nq].len=st[p].len+;
st[np].fa=st[q].fa=nq;
while(p && st[p].trans[ch]==q) st[p].trans[ch]=nq, p=st[p].fa;
}
}
}
inline void prepare()
{
for(int i=;i<=tott;i++) cnt[st[i].len]++;
for(int i=;i<=n;i++) cnt[i]+=cnt[i-];
for(int i=tott;i;i--) rank[cnt[st[i].len]--]=i;
for(int i=tott;i;i--) size[st[rank[i]].fa]+=size[rank[i]];
size[]=size[]=;
for(int i=;i<=tott;i++)
sum[rank[i]]=1ll*size[rank[i]]*(st[rank[i]].len-st[st[rank[i]].fa].len)+sum[st[rank[i]].fa];
}
int main()
{
scanf("%s", s1+); n=strlen(s1+);
scanf("%s", s2+); m=strlen(s2+);
now=root=tott=;
for(int i=;i<=n;i++) extend(s1[i]-'a');
prepare();
int x=root, len=;
for(int i=;i<=m;i++)
{
int ch=s2[i]-'a';
if(st[x].trans[ch]) len++, x=st[x].trans[ch];
else
{
while(x && !st[x].trans[ch]) x=st[x].fa;
if(!x) x=root, len=;
else len=st[x].len+, x=st[x].trans[ch];
}
ans+=sum[st[x].fa]+1ll*size[x]*(len-st[st[x].fa].len);
}
printf("%lld\n", ans);
}

  11.bzoj2780: [Spoj]8093 Sevenk Love Oimaster

   广义SAM。

   把所有串建在一个SAM里,用分割符隔开。暴力记录每个r是属于第几个串里的,然后每次询问只需要找到这个串的状态,找到子树里不同数的个数。

   有两个做法:

   ①dsu on tree 第一眼的做法,跑得比较慢

#include<iostream>
#include<cstring>
#include<cstdlib>
#include<cstdio>
#define ll long long
using namespace std;
const int maxn=, inf=1e9;
struct sam{int fa, len, trans[];}st[maxn];
struct poi{int too, pre;}e[maxn];
int n, m, now, root, tott, tot, skip, nowlen;
int cnt[maxn], r[maxn], rank[maxn], size[maxn], last[maxn], tsize[maxn], son[maxn], pos[maxn];
ll ans[maxn], sum;
char s[maxn];
inline void add(int x, int y){e[++tot]=(poi){y, last[x]}; last[x]=tot;}
inline void extend(int ch)
{
int np=++tott, p=now;
st[np].len=st[p].len+; now=np; r[np]=nowlen;
while(p && !st[p].trans[ch]) st[p].trans[ch]=np, p=st[p].fa;
if(!p) st[np].fa=root;
else
{
int q=st[p].trans[ch];
if(st[p].len+==st[q].len) st[np].fa=q;
else
{
int nq=++tott;
st[nq]=st[q];
st[nq].len=st[p].len+;
st[np].fa=st[q].fa=nq;
while(p && st[p].trans[ch]==q) st[p].trans[ch]=nq, p=st[p].fa;
}
}
}
void dfs1(int x)
{
tsize[x]=;
for(int i=last[x], too;i;i=e[i].pre)
{
dfs1(too=e[i].too); tsize[x]+=tsize[too];
if(tsize[too]>tsize[son[x]]) son[x]=too;
}
}
void update(int x, int delta)
{
if(delta) sum+=(!cnt[pos[r[x]]] && pos[r[x]]), cnt[pos[r[x]]]++;
else cnt[pos[r[x]]]--;
for(int i=last[x], too;i;i=e[i].pre)
if((too=e[i].too)!=skip) update(too, delta);
}
void dfs(int x, bool heavy)
{
for(int i=last[x], too;i;i=e[i].pre)
if((too=e[i].too)!=son[x]) dfs(too, );
if(son[x]) dfs(son[x], ), skip=son[x];
update(x, ); skip=; ans[x]=sum;
if(!heavy) update(x, ), sum=;
}
int main()
{
scanf("%d%d", &n, &m);
root=now=tott=;
for(int i=;i<=n;i++)
{
scanf("%s", s+); int len=strlen(s+);
++nowlen; extend();
for(int j=;j<=len;j++) pos[++nowlen]=i, extend(s[j]-'a');
}
for(int i=;i<=tott;i++) add(st[i].fa, i);
dfs1(); dfs(, );
for(int i=;i<=m;i++)
{
int x=root;
scanf("%s", s+); int len=strlen(s+), flag=;
for(int j=;j<=len;j++)
if(!st[x].trans[s[j]-'a']){flag=; puts(""); break;}
else x=st[x].trans[s[j]-'a'];
if(!flag) printf("%lld\n", ans[x]);
}
}

   ②dfs序排序后树状数组,查询一个子树相当于查询一段区间里不同数的个数,在线的话就用主席树,离线的话把主席树的做法套过来就行了。要查询一段区间$[l,r]$,相当于查询区间内有多少个数,满足上一个这种数的位置$<l$,用一个权值BIT就能解决了。

#include<iostream>
#include<cstring>
#include<cstdlib>
#include<cstdio>
#define ll long long
using namespace std;
const int maxn=, inf=1e9;
struct sam{int fa, len, trans[];}st[maxn];
struct poi{int too, pre;}e[maxn];
struct tjm{int too, l, pre;}q[maxn];
int n, m, now, root, tott, tot, totque, nowdfn, nowlen;
int r[maxn], last[maxn], pos[maxn], dfnl[maxn], dfnr[maxn], lastque[maxn], tree[maxn], pre[maxn], dfnpos[maxn], nowcnt[maxn];
ll ans[maxn], sum;
char s[maxn];
inline void add(int x, int y){e[++tot]=(poi){y, last[x]}; last[x]=tot;}
inline void addque(int x, int y, int z){q[++totque]=(tjm){y, z, lastque[x]}; lastque[x]=totque;}
inline void update(int x, int delta){for(;x<=tott;x+=x&-x) tree[x]+=delta;}
inline int query(int x){sum=; for(;x;x-=x&-x) sum+=tree[x]; return sum;}
inline void extend(int ch)
{
int np=++tott, p=now;
st[np].len=st[p].len+; now=np; r[np]=nowlen;
while(p && !st[p].trans[ch]) st[p].trans[ch]=np, p=st[p].fa;
if(!p) st[np].fa=root;
else
{
int q=st[p].trans[ch];
if(st[p].len+==st[q].len) st[np].fa=q;
else
{
int nq=++tott;
st[nq]=st[q];
st[nq].len=st[p].len+;
st[np].fa=st[q].fa=nq;
while(p && st[p].trans[ch]==q) st[p].trans[ch]=nq, p=st[p].fa;
}
}
}
void dfs1(int x)
{
dfnl[x]=++nowdfn; dfnpos[nowdfn]=r[x];
for(int i=last[x];i;i=e[i].pre) dfs1(e[i].too);
dfnr[x]=nowdfn;
}
int main()
{
scanf("%d%d", &n, &m);
root=now=tott=;
for(int i=;i<=n;i++)
{
scanf("%s", s+); int len=strlen(s+);
++nowlen; extend();
for(int j=;j<=len;j++) pos[++nowlen]=i, extend(s[j]-'a');
}
for(int i=;i<=tott;i++) add(st[i].fa, i); dfs1();
for(int i=;i<=m;i++)
{
int x=root;
scanf("%s", s+); int len=strlen(s+), flag=;
for(int j=;j<=len;j++)
if(!st[x].trans[s[j]-'a']){flag=; break;}
else x=st[x].trans[s[j]-'a'];
if(!flag) addque(dfnr[x], i, dfnl[x]);
}
for(int i=;i<=tott;i++)
{
nowcnt[i]=nowcnt[i-];
if(pos[dfnpos[i]]) update(pre[pos[dfnpos[i]]]+, ), pre[pos[dfnpos[i]]]=i, nowcnt[i]++;
for(int j=lastque[i], too;j;j=q[j].pre)
ans[too=q[j].too]+=query(q[j].l)-nowcnt[q[j].l-];
}
for(int i=;i<=m;i++) printf("%lld\n", ans[i]);
}

  12.bzoj3473: 字符串 && bzoj3277: 串

   广义SAM。

   这题按上一题的做法就不可行了,因为我们需要知道对于每一个字符串的答案是多少。

   建广义SAM的方法就是对于每一个串,都从根节点开始建,建在同一个SAM上。

   对于这题,对于每一个能达到的状态,给它到root的路径上所有点的贡献都+1,遇到加过的点就退出,最坏复杂度$O(n\sqrt n)$。

   最后某个字符串的答案就是所有前缀的状态到root的路径上的权值和的和。

#include<iostream>
#include<cstring>
#include<cstdlib>
#include<cstdio>
#include<cstring>
#define ll long long
using namespace std;
const int maxn=;
struct sam{int len, fa, trans[];}st[maxn];
int n, k, x, now, root, tott, mxlen, v[maxn], rank[maxn], cnt[maxn];
ll ans, cntans[maxn];
char tmp[maxn];
string s[maxn>>];
inline void extend(int ch)
{
int np=++tott, p=now;
st[np].len=st[p].len+; now=np;
while(p && !st[p].trans[ch]) st[p].trans[ch]=np, p=st[p].fa;
if(!p) st[np].fa=root;
else
{
int q=st[p].trans[ch];
if(st[p].len+==st[q].len) st[np].fa=q;
else
{
int nq=++tott;
st[nq]=st[q];
st[nq].len=st[p].len+;
st[np].fa=st[q].fa=nq;
while(p && st[p].trans[ch]==q) st[p].trans[ch]=nq, p=st[p].fa;
}
}
}
int main()
{
scanf("%d%d", &n, &k); now=root=tott=;
for(int i=;i<=n;i++)
{
scanf("%s", tmp); s[i]=(string)tmp;
int len=strlen(tmp); now=root;
for(int j=;j<len;j++) extend(s[i][j]-'a');
}
mxlen=;
for(int i=;i<=n;i++)
{
int len=s[i].length(), x=root; mxlen=max(mxlen, len);
for(int j=;j<len;j++)
{
x=st[x].trans[s[i][j]-'a']; int p=x;
while(p && v[p]!=i) cntans[p]++, v[p]=i, p=st[p].fa;
}
}
for(int i=;i<=tott;i++) cntans[i]=(cntans[i]>=k)*(st[i].len-st[st[i].fa].len);
for(int i=;i<=tott;i++) cnt[st[i].len]++;
for(int i=;i<=mxlen;i++) cnt[i]+=cnt[i-];
for(int i=tott;i;i--) rank[cnt[st[i].len]--]=i;
for(int i=;i<=tott;i++) cntans[rank[i]]+=cntans[st[rank[i]].fa];
for(int i=;i<=n;i++)
{
int len=s[i].length(); x=root; ll ans=;
for(int j=;j<len;j++) x=st[x].trans[s[i][j]-'a'], ans+=cntans[x];
printf("%lld ", ans);
}
}

  13.bzoj3926: [Zjoi2015]诸神眷顾的幻想乡

   因为叶子节点只有20个,那么对每一个叶子节点都建一棵trie,树上的一个子串一定是某棵trie上根到叶子路径上的某一段直线,所以只需要把这20棵trie都丢到SAM上,查一下本质不同的子串个数就好了,这个显然是\sum max(s)-min(s)+1

   提一下怎么把trie建成SAM,直接dfs,然后对于一个点,从这个点的状态出发把所有儿子加进SAM即可

#include<iostream>
#include<cstdlib>
#include<cstdio>
#include<cstring>
#define ll long long
using namespace std;
const int maxn=, inf=1e9;
struct sam{int len, fa, trans[];}st[maxn];
struct poi{int too, pre;}e[maxn];
int n, c, x, y, tot, root, now, tott, np;
int col[maxn], tim[maxn], cnt[maxn], last[maxn];
ll ans;
inline void read(int &k)
{
int f=; k=; char c=getchar();
while(c<'' || c>'') c=='-' && (f=-), c=getchar();
while(c<='' && c>='') k=k*+c-'', c=getchar();
k*=f;
}
inline void add(int x, int y){e[++tot]=(poi){y, last[x]}; last[x]=tot;}
inline void extend(int ch)
{
int p=now; np=++tott;
st[np].len=st[p].len+; now=np;
while(p && !st[p].trans[ch]) st[p].trans[ch]=np, p=st[p].fa;
if(!p) st[np].fa=root;
else
{
int q=st[p].trans[ch];
if(st[p].len+==st[q].len) st[np].fa=q;
else
{
int nq=++tott;
st[nq]=st[q];
st[nq].len=st[p].len+;
st[np].fa=st[q].fa=nq;
while(p && st[p].trans[ch]==q) st[p].trans[ch]=nq, p=st[p].fa;
}
}
}
void dfs(int x, int fa)
{
extend(col[x]); int pos=np;
for(int i=last[x], too;i;i=e[i].pre)
if((too=e[i].too)!=fa) now=pos, dfs(too, x);
}
int main()
{
read(n); read(c);
for(int i=;i<=n;i++) read(col[i]);
for(int i=;i<n;i++) read(x), read(y), add(x, y), add(y, x), cnt[x]++, cnt[y]++;
now=root=tott=; dfs(, );
for(int i=;i<=n;i++) if(cnt[i]==) now=root, dfs(i, );
for(int i=;i<=tott;i++) ans+=st[i].len-st[st[i].fa].len;
printf("%lld\n", ans);
}

  14.hackerrank Special Substrings

   大爷说必须写树剖。。然而我YY了个只写倍增的做法也过了(调了4h...不够冷静,最开始的思路是对的到后来却否定掉写成错误思路...

   SAM难以做到求回文串,先用manacher跑出来,一个点最多会生成一个本质不同的回文串,于是先用manacher跑出来记录一下即可。

   把字符串倒着插入SAM,记录一下每个r的状态编号,并处理每个状态的$2^i$倍祖先。然后从左到右扫,每次如果当前位置有新的本质不同的回文串,就倍增找到左端点的状态的祖先中第一个$len\geq$回文串长度的,那么从他开始到根的路径上的点都是有贡献的,但问题是有可能已经贡献过了,所以需要判重。

   判重的时候需要注意一下,就是我们倍增找到的第一个节点代表的子串可能并不都有贡献,因为有可能一些子串长度比当前回文串长,所以我们还需要记录一下每个状态被贡献了多少子串,当一个状态贡献完所有子串后它就没必要再访问了。因为每次查询一个回文串最多只有一个状态没有贡献所有子串,所以每个状态最多被遍历两次,复杂度$O(nlogn)$。

#include<iostream>
#include<cstring>
#include<cstdlib>
#include<cstdio>
#define ll long long
using namespace std;
const int maxn=, inf=1e9;
struct sam{int fa, len, trans[];}st[maxn];
struct poi{int too, pre;}e[maxn];
int n, root, now, tott, tot;
int pos[maxn], p[maxn], fa[][maxn], len[maxn], v[maxn], last[maxn];
ll ans;
char s[maxn];
inline void add(int x, int y){e[++tot]=(poi){y, last[x]}; last[x]=tot;}
inline void extend(int x, int ch)
{
int np=++tott, p=now;
st[np].len=st[p].len+; now=np; pos[x]=np;
while(p && !st[p].trans[ch]) st[p].trans[ch]=np, p=st[p].fa;
if(!p) st[np].fa=root;
else
{
int q=st[p].trans[ch];
if(st[p].len+==st[q].len) st[np].fa=q;
else
{
int nq=++tott;
st[nq]=st[q];
st[nq].len=st[p].len+;
st[np].fa=st[q].fa=nq;
while(p && st[p].trans[ch]==q) st[p].trans[ch]=nq, p=st[p].fa;
}
}
}
inline void query(int x, int len)
{
for(int i=;~i;i--) if(st[fa[i][x]].len>=len) x=fa[i][x];
while(x && len>v[x] && st[x].len>v[x]) ans+=min(len, st[x].len)-v[x], v[x]=len, x=st[x].fa;
}
int main()
{
scanf("%d", &n); scanf("%s", s+); root=now=tott=;
for(int i=n;i;i--) extend(i, s[i]-'a');
s[]='$'; s[n+]='#';
int mid=, mx=;
for(int i=;i<=n;i++)
{
if(mx>i) p[i]=min(p[(mid<<)-i], mx-i); else p[i]=, len[i]=max(len[i], );
while(s[i-p[i]]==s[i+p[i]]) p[i]++, len[i+p[i]-]=max(len[i+p[i]-], (p[i]<<)-);
if(p[i]+i>mx) mx=p[i]+i, mid=i;
}
mid=mx=;
for(int i=;i<=n;i++)
{
if(mx>i) p[i]=min(p[(mid<<)-i], mx-i); else p[i]=;
while(s[i-p[i]]==s[i+p[i]+]) p[i]++, len[i+p[i]]=max(len[i+p[i]], p[i]<<);
if(p[i]+i>mx) mx=p[i]+i, mid=i;
}
for(int i=;i<=tott;i++) fa[][i]=st[i].fa, v[i]=st[st[i].fa].len;
for(int j=;j<=;j++) for(int i=;i<=tott;i++) fa[j][i]=fa[j-][fa[j-][i]];
for(int i=;i<=n;i++, printf("%lld\n", ans)) query(pos[i-len[i]+], len[i]);
}

  15.bzoj2806: [Ctsc2012]Cheat

   先建广义SAM。

   对于每一个询问的串求出向左最远能匹配多少,基本操作就不说了,求最长公共子串一样的方法。

   显然是二分题,考虑怎么check。

   DP。设$f(i)$为到第i个位置最多能匹配几位,则有$f(i)=max(f(i-1),max(f(j)+i-j)) \{j<=i-mid,j>=i-$最远匹配距离$\}$

   显然最远匹配位置递增,所以是可以用单调队列优化的。

   写的很快。

#include<iostream>
#include<cstring>
#include<cstdlib>
#include<cstdio>
#define ll long long
using namespace std;
const int maxn=, inf=1e9;
struct sam{int len, fa, trans[];}st[maxn];
int n, m, tott, root, now, len;
int q[maxn], dp[maxn], cnt[maxn];
char s[maxn];
inline void extend(int ch)
{
int np=++tott, p=now;
st[np].len=st[p].len+; now=np;
while(p && !st[p].trans[ch]) st[p].trans[ch]=np, p=st[p].fa;
if(!p) st[np].fa=root;
else
{
int q=st[p].trans[ch];
if(st[p].len+==st[q].len) st[np].fa=q;
else
{
int nq=++tott;
st[nq]=st[q];
st[nq].len=st[p].len+;
st[np].fa=st[q].fa=nq;
while(p && st[p].trans[ch]==q) st[p].trans[ch]=nq, p=st[p].fa;
}
}
}
bool check(int mid)
{
int l=, r=, ans=;
for(int i=;i<=len;i++)
{
if(i>=mid)
{
while(l<=r && dp[q[r]]-q[r]<=dp[i-mid]-(i-mid)) r--;
q[++r]=i-mid;
}
dp[i]=dp[i-];
while(l<=r && q[l]<i-cnt[i]) l++;
if(l<=r) dp[i]=max(dp[i], dp[q[l]]+i-q[l]);
ans=max(ans, dp[i]);
}
return *ans>=*len;
}
int main()
{
scanf("%d%d", &n, &m); root=now=tott=;
for(int i=;i<=m;i++)
{
scanf("%s", s+);
now=root; len=strlen(s+);
for(int j=;j<=len;j++) extend(s[j]-'');
}
for(int i=;i<=n;i++)
{
scanf("%s", s+);
len=strlen(s+); int x=root;
for(int j=;j<=len;j++)
if(st[x].trans[s[j]-'']) x=st[x].trans[s[j]-''], cnt[j]=cnt[j-]+;
else
{
while(x && !st[x].trans[s[j]-'']) x=st[x].fa;
if(!x) x=root, cnt[j]=;
else cnt[j]=st[x].len+, x=st[x].trans[s[j]-''];
}
int l=, r=len;
while(l<r)
{
int mid=(l+r+)>>;
if(check(mid)) l=mid;
else r=mid-;
}
printf("%d\n", l);
}
}

  16.bzoj1396: 识别子串

   傻逼题了,一眼1A...

   就是size==1的那些状态,更新一下被覆盖的位置,用线段树维护即可。

#include<iostream>
#include<cstring>
#include<cstdlib>
#include<cstdio>
using namespace std;
const int maxn=, inf=1e9;
struct sam{int len, fa, trans[];}st[maxn];
struct poi{int mn[], delta[];}tree[maxn<<];
int n, tott, root, now;
int cnt[maxn], rank[maxn], size[maxn], pos[maxn];
char s[maxn];
inline int min(int a, int b){return a<b?a:b;}
inline void extend(int x, int ch)
{
int np=++tott, p=now;
st[np].len=st[p].len+; now=np; size[np]=; pos[np]=x;
while(p && !st[p].trans[ch]) st[p].trans[ch]=np, p=st[p].fa;
if(!p) st[np].fa=root;
else
{
int q=st[p].trans[ch];
if(st[p].len+==st[q].len) st[np].fa=q;
else
{
int nq=++tott;
st[nq]=st[q];
st[nq].len=st[p].len+;
st[np].fa=st[q].fa=nq;
while(p && st[p].trans[ch]==q) st[p].trans[ch]=nq, p=st[p].fa;
}
}
}
void build(int x, int l, int r)
{
tree[x].mn[]=tree[x].mn[]=tree[x].delta[]=tree[x].delta[]=inf;
if(l==r) return;
int mid=(l+r)>>;
build(x<<, l, mid); build(x<<|, mid+, r);
}
inline void minone(int x, int delta, int ty)
{
tree[x].mn[ty]=min(tree[x].mn[ty], delta);
tree[x].delta[ty]=min(tree[x].delta[ty], delta);
}
inline void down(int x, int ty)
{
if(tree[x].delta[ty]==inf) return;
minone(x<<, tree[x].delta[ty], ty);
minone(x<<|, tree[x].delta[ty], ty);
tree[x].delta[ty]=inf;
}
inline void up(int x, int ty){tree[x].mn[ty]=min(tree[x<<].mn[ty], tree[x<<|].mn[ty]);}
void update(int x, int l, int r, int cl, int cr, int delta, int ty)
{
if(l!=r) down(x, ty);
if(cl<=l && r<=cr)
{
tree[x].mn[ty]=min(tree[x].mn[ty], delta);
tree[x].delta[ty]=delta;
return;
}
int mid=(l+r)>>;
if(cl<=mid) update(x<<, l, mid, cl, cr, delta, ty);
if(cr>mid) update(x<<|, mid+, r, cl, cr, delta, ty);
up(x, ty);
}
int query(int x, int l, int r, int cx, int ty)
{
if(l==r) return tree[x].mn[ty];
down(x, ty);
int mid=(l+r)>>;
if(cx<=mid) return query(x<<, l, mid, cx, ty);
return query(x<<|, mid+, r, cx, ty);
}
int main()
{
scanf("%s", s+); n=strlen(s+);
build(, , n); tott=root=now=;
for(int i=;i<=n;i++) extend(i, s[i]-'a');
for(int i=;i<=tott;i++) cnt[st[i].len]++;
for(int i=;i<=n;i++) cnt[i]+=cnt[i-];
for(int i=tott;i;i--) rank[cnt[st[i].len]--]=i;
for(int i=tott;i;i--) size[st[rank[i]].fa]+=size[rank[i]];
for(int i=;i<=tott;i++)
if(size[i]==)
{
update(, , n, pos[i]-st[i].len+, pos[i]-st[st[i].fa].len, pos[i], );
update(, , n, pos[i]-st[st[i].fa].len+, pos[i], st[st[i].fa].len+, );
}
for(int i=;i<=n;i++)
printf("%d\n", min(query(, , n, i, )-i+, query(, , n, i, )));
}

【算法】后缀自动机(SAM) 例题的更多相关文章

  1. 【算法】后缀自动机(SAM) 初探

    [自动机] 有限状态自动机的功能是识别字符串,自动机A能识别字符串S,就记为$A(S)$=true,否则$A(S)$=false. 自动机由$alpha$(字符集),$state$(状态集合),$in ...

  2. 后缀自动机(SAM)奶妈式教程

    后缀自动机(SAM) 为了方便,我们做出如下约定: "后缀自动机" (Suffix Automaton) 在后文中简称为 SAM . 记 \(|S|\) 为字符串 \(S\) 的长 ...

  3. [转]后缀自动机(SAM)

    原文地址:http://blog.sina.com.cn/s/blog_8fcd775901019mi4.html 感觉自己看这个终于觉得能看懂了!也能感受到后缀自动机究竟是一种怎样进行的数据结构了. ...

  4. SPOJ 1811. Longest Common Substring (LCS,两个字符串的最长公共子串, 后缀自动机SAM)

    1811. Longest Common Substring Problem code: LCS A string is finite sequence of characters over a no ...

  5. 后缀自动机SAM学习笔记

    前言(2019.1.6) 已经是二周目了呢... 之前还是有一些东西没有理解到位 重新写一下吧 后缀自动机的一些基本概念 参考资料和例子 from hihocoder DZYO神仙翻译的神仙论文 简而 ...

  6. 浅谈后缀自动机SAM

    一下是蒟蒻的个人想法,并不很严谨,仅供参考,如有缺误,敬请提出 参考资料: 陈立杰原版课件 litble 某大神 某大神 其实课件讲得最详实了 有限状态自动机 我们要学后缀自动机,我们先来了解一下自动 ...

  7. 【算法专题】后缀自动机SAM

    后缀自动机是用于识别子串的自动机. 学习推荐:陈立杰讲稿,本文记录重点部分和感性理解(论文语言比较严格). 刷题推荐:[后缀自动机初探],题目都来自BZOJ. [Right集合] 后缀自动机真正优于后 ...

  8. 后缀自动机(SAM)速成手册!

    正好写这个博客和我的某个别的需求重合了...我就来讲一讲SAM啦qwq 后缀自动机,也就是SAM,是一种极其有用的处理字符串的数据结构,可以用于处理几乎任何有关于子串的问题,但以学起来异常困难著称(在 ...

  9. 后缀自动机SAM

    某神犇:"初三还不会后缀自动机,那就退役吧!" 听到这句话后,我的内心是崩溃的. 我还年轻,我还不想退役--于是,我在后来,努力地学习后缀自动机. 终于,赶在初三开学前,我终于学会 ...

随机推荐

  1. Java编程思想 学习笔记10

    十.内部类  可以将一个类的定义放在另一个类的定义内部,这就是内部类. 内部类是一种非常有用的特性,因为它允许你把一些逻辑相关的类组织在一起,并控制位于内部的类的可视性.然而必须要了解,内部类和组合是 ...

  2. Said

    呃~~生活中的每天都会经历很多事儿,而影响结果的无非就是人对事物的处理方式和对事物的处理态度~~ 在上学期间,有时考试不理想,我都会进行反思,对不该错的题巩固在三,对不会做的题查缺补漏……因为不能不思 ...

  3. css去除苹果默认样式

    input[type="button"], input[type="submit"], input[type="reset"] { -web ...

  4. MySQL事物(一)事务隔离级别和事物并发冲突

    数据库的操作通常为写和读,就是所说的CRUD:增加(Create).读取(Read).更新(Update)和删除(Delete).事务就是一件完整要做的事情.事务是恢复和并发控制的基本单位.事务必须始 ...

  5. C# 面向对象的new关键字的使用

    using System; using System.Collections.Generic; using System.Linq; using System.Text; namespace Cons ...

  6. Qt之QEvent(所有事件的翻译)

    QEvent 类是所有事件类的基类,事件对象包含事件参数. Qt 的主事件循环(QCoreApplication::exec())从事件队列中获取本地窗口系统事件,将它们转化为 QEvents,然后将 ...

  7. PhoneUtil

    package cn.fraudmetrix.octopus.horai.biz.utils; import org.springframework.util.StringUtils; import ...

  8. 在使用NSArray打印的时候如果遇到中文字符那么会打印出来编码。

    在使用NSArray打印的时候如果遇到中文字符那么会打印出来编码,如下代码: - (void)viewDidLoad { [super viewDidLoad]; // Do any addition ...

  9. 2018-2019-2 网络对抗技术 20165230 Exp5 MSF基础应用

    目录 1.实验内容 2.基础问题回答 3.实验内容 任务一:一个主动攻击实践 漏洞MS08_067(成功) 任务二:一个针对浏览器的攻击 ms11_050(成功) ms14_064(成功) 任务三:一 ...

  10. Django 聚合与查询集API实现侧边栏

    本文从Django官方文档总结而来,将聚合的主要用法和查询集的常见方法做一归纳. 聚合 1. 聚合的产生来源于django数据库查询,通常我们使用django查询来完成增删查改,但是有时候需要更复杂的 ...