①.标记法
直接对 n n n个串建立广义 S A M SAM SAM
如果是第一个串插入产生的节点标记为 0 0 0,其余串插入的节点标记为 1 1 1
然后由于祖先节点都是自己的后缀,如果自己属于第一个串,那么祖先也是第一个串
所以基数排序一遍,把标记传递给祖先节点
于是,每个点是否独立属于第一个串我们就知道了。
我们从自动机的根节点开始 b f s bfs bfs,保存到达每个点的最短路
第一个搜到的合法节点(只在第一个串出现),就是我们的答案,途经的转移边就是子串
正版广义SAM(特判重复节点版本)
#include <bits/stdc++.h>
using namespace std;
const int maxn = 1e6+10;
int casenum;
int zi[maxn][27],fa[maxn],l[maxn],id=1,las=1,f[maxn];
char a[maxn];
void insert(int c,int ok)
{
int p = las;
if( zi[p][c] )//重复节点,所以我们需要把q,nq都染色,因为这次并没有新建节点!!
{
int q = zi[p][c];
if( l[q]==l[p]+1 ) las = q,f[q] = ok;
else
{
int nq = ++id; las = nq;
memcpy( zi[nq],zi[q],sizeof zi[nq] );
fa[nq] = fa[q], l[nq] = l[p]+1; f[nq] = f[q] | ok;
fa[q] = nq;
for( ; p&&zi[p][c]==q;p=fa[p] ) zi[p][c] = nq;
}
}
else
{
int np = ++id; las = id;
l[np] = l[p]+1; f[np] = ok;
for( ; p&&zi[p][c]==0 ;p=fa[p] ) zi[p][c] = np;
if( p==0 ) fa[np] = 1;
else
{
int q = zi[p][c];
if( l[q]==l[p]+1 ) fa[np] = q;
else
{
int nq = ++id;
memcpy( zi[nq],zi[q],sizeof zi[nq] );
l[nq] = l[p]+1, fa[nq] = fa[q]; f[nq] = f[q];
fa[q] = fa[np] = nq;
for( ; p&&zi[p][c]==q; p=fa[p] ) zi[p][c] = nq;
}
}
}
}
int c[maxn],rk[maxn];
void chiken_sort()
{
for(int i=1;i<=id;i++) c[l[i]]++;
for(int i=1;i<=id;i++) c[i] += c[i-1];
for(int i=1;i<=id;i++) rk[c[l[i]]--] = i;
for(int i=id;i>=1;i--)
{
int u = rk[i];
f[fa[u]] |= f[u];
}
}
void build(int ok)
{
las = 1;
scanf("%s",a+1 ); int n = strlen( a+1 );
for(int i=1;i<=n;i++) insert( a[i]-'a',ok );
}
int pre[maxn],vis[maxn];
char ans[maxn];
void dfs(int ed)
{
if( ed==1 ) return;
dfs( pre[ed] );
printf("%c",ans[ed] );
}
void solve()
{
queue<int>q; q.push( 1 );
int ed = 0;
for(int i=1;i<=id;i++) vis[i] = 0;
while( !q.empty() )
{
int u = q.front(); q.pop();
if( f[u]==0 ) { ed = u; break; }
for(int i=0;i<=25;i++)
{
int v = zi[u][i];
if( v==0 || vis[v] ) continue;
vis[v] = 1;
ans[v] = 'a'+i; pre[v] = u; q.push( v );
}
}
printf("Case #%d: ",++casenum);
if( ed==0 ) puts("Impossible");
else { dfs(ed); cout << '\n'; }
}
int main()
{
int t; cin >> t;
while( t-- )
{
int n; cin >> n;
build( 0 );
for(int i=2;i<=n;i++) build( 1 );
chiken_sort();
solve();
for(int i=1;i<=id;i++)
{
memset( zi[i],0,sizeof zi[i] );
f[i] = fa[i] = l[i] = c[i] = rk[i] = 0;
}
las = id = 1;
}
}
普通广义 S A M SAM SAM(无特判)
#include <bits/stdc++.h>
using namespace std;
const int maxn = 1e6+10;
int casenum;
int zi[maxn][27],fa[maxn],l[maxn],id=1,las=1,f[maxn];
char a[maxn];
void insert(int c,int ok)
{
int p = las,np = ++id; las = id;
l[np] = l[p]+1; f[np] = ok;
for( ; p&&zi[p][c]==0 ;p=fa[p] ) zi[p][c] = np;
if( p==0 ) fa[np] = 1;
else
{
int q = zi[p][c];
if( l[q]==l[p]+1 ) fa[np] = q;
else
{
int nq = ++id;
memcpy( zi[nq],zi[q],sizeof zi[nq] );
l[nq] = l[p]+1, fa[nq] = fa[q]; f[nq] = f[q];
fa[q] = fa[np] = nq;
for( ; p&&zi[p][c]==q; p=fa[p] ) zi[p][c] = nq;
}
}
}
vector<int>vec[maxn];
void dfs_fail(int u)
{
for( auto v:vec[u] )
{
dfs_fail(v);
f[u] |= f[v];
}
}
void build(int ok)
{
las = 1;
scanf("%s",a+1 ); int n = strlen( a+1 );
for(int i=1;i<=n;i++) insert( a[i]-'a',ok );
}
int pre[maxn],vis[maxn];
char ans[maxn];
void dfs(int ed)
{
if( ed==1 ) return;
dfs( pre[ed] );
printf("%c",ans[ed] );
}
void solve()
{
queue<int>q; q.push( 1 );
int ed = 0;
for(int i=1;i<=id;i++) vis[i] = 0;
while( !q.empty() )
{
int u = q.front(); q.pop();
if( f[u]==0 ) { ed = u; break; }
for(int i=0;i<=25;i++)
{
int v = zi[u][i];
if( v==0 || vis[v] ) continue;
vis[v] = 1;
ans[v] = 'a'+i; pre[v] = u; q.push( v );
}
}
printf("Case #%d: ",++casenum);
if( ed==0 ) puts("Impossible");
else { dfs(ed); cout << '\n'; }
}
int main()
{
int t; cin >> t;
while( t-- )
{
int n; cin >> n;
build( 0 );
for(int i=2;i<=n;i++) build( 1 );
for(int i=2;i<=id;i++) vec[fa[i]].push_back( i );
dfs_fail(1);
solve();
for(int i=1;i<=id;i++)
{
vec[i].clear();
memset( zi[i],0,sizeof zi[i] );
f[i] = fa[i] = l[i] = 0;
}
las = id = 1;
}
}
②.动态匹配法
我们只把后 n − 1 n-1 n−1个串建立广义 S A M SAM SAM
拿第一个串在自动机上跑即可
当我们插入完 i i i节点后发现失配了,之前的匹配节点是 x x x,匹配长度是 l l l
我们就需要往上跳 f a fa fa去匹配,如果仍然没有转移边就重复跳 f a fa fa
向上跳跃的过程中,我们经过了若干个没有转移边的节点
记这些节点为 p p p,其中最短的子串为 l [ f a p ] + 1 l[fa_p]+1 l[fap]+1,那么最短 l [ f a p ] + 2 l[fa_p]+2 l[fap]+2就会适配(加上当前的 a i a_i ai字符)
最后更新一下就好了
#define others
#ifdef poj
#include <iostream>
#include <cstring>
#include <cmath>
#include <cstdio>
#include <algorithm>
#include <vector>
#include <string>
#include <map>
#include <set>
#endif // poj
#ifdef others
#include <bits/stdc++.h>
#endif // others
//#define file
#define all(x) x.begin(), x.end()
using namespace std;
#define eps 1e-8
const double pi = acos(-1.0);
typedef long long LL;
typedef unsigned long long ULL;
void umax(int &a, int b) {
a = max(a, b);
}
void umin(int &a, int b) {
a = min(a, b);
}
int dcmp(double x) {
return fabs(x) <= eps?0:(x > 0?1:-1);
}
namespace solver {
int res = 1e9, pos = 0;
string s, t;
namespace SAM{
static const int maxnode = 2e6+10;//至少开两倍
static const int maxn = 27;
int p, q, np, nq;
int cnt, ST_T, len;
vector<int>G;
int trans[maxnode][maxn], l[maxnode], fa[maxnode];
int newnode() {
int x = ++cnt;
memset(trans[x], 0, sizeof trans[x]);
fa[x] = 0;
l[x] = 0;
return cnt;
}
void init() {
cnt = 0;
ST_T = newnode();
}
void add(int c) {
//令当前串为T,新加的字符为x。
p = ST_T; np = ST_T = newnode(); l[np] = l[p] + 1;//令p = ST(T),新建np = ST(Tx)
while(!trans[p][c]&&p) trans[p][c] = np, p = fa[p];//对于p的所有没有标号c的边的祖先v,trans[v][c] = np。
if(!p) fa[np] = 1; //找到p的第一个祖先vp,他有标号c的边,如果没有这样的vp,那么fa[p]=root,结束该阶段。
else {
q = trans[p][c];//令q=trans[vp][c]
if(l[p] + 1 == l[q]) fa[np] = q;//若l[q] = l[vp] + 1,令fa[np] = q,结束该阶段。
else {
nq = newnode(); l[nq] = l[p] + 1;//否则建立新节点nq
memcpy(trans[nq],trans[q],sizeof(trans[q]));//trans(nq, *) = trans(q, *)
fa[nq] = fa[q];
fa[np] = fa[q] = nq;
while(trans[p][c] == q) trans[p][c] = nq, p = fa[p];//对于所有的trans(v, c) == q的p的祖先v, trans(v, c)改为nq。
}
}
}
void build(string &str) {
len = str.size();
for(int i = 0; i < len; i++)
add(str[i]-'a');
}
void solve(string &str) {
int x = 0, p = 1;
for(int i = 0; i < str.size(); i++) {
if(trans[p][str[i]-'a']) {
p = trans[p][str[i]-'a'];
x = l[fa[p]] + 1;
} else {
int now = 0;
while(p && !trans[p][str[i]-'a']) {//失配,之前的匹配长度为x
now = x+1;
p = fa[p];
x = l[fa[p]] + 1;
}
if( now )//保存最短的不可匹配长度
{
if(res > now )
{//长度更小
res = now;
pos = i;
}
else if(res == now)
{
if(s.substr(i - res + 1, res) < s.substr(pos - res + 1, res))
pos = i;
}
}
if(p == 0) {
x = 0, p = 1;
} else {
p = trans[p][str[i]-'a'];
x = l[fa[p]]+1;
}
}
}
}
};
int tt;
void solve() {
cin >> tt;
int f = 0;
while(tt--) {
res = 1e9;
int n;
cin >> n;
cin >> s;
cin >> t;
for(int i = 2; i < n; i++)
{
string tmp;
t += 'z'+1;
cin >> tmp;
t += tmp;
}
SAM::init();
SAM::build(t);
SAM::solve(s);
cout<<"Case #"<<++f<<": ";
if(res == 1e9) {
cout<<"Impossible"<<'\n';
} else {
cout<<s.substr(pos-res+1, res)<<'\n';
}
}
}
}
int main() {
solver::solve();
return 0;
}