【算法】后缀自动机(SAM) 例题

算法介绍见: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)速成手册!


下一篇:详解联想bios怎么进入u盘启动