题解 UVA11107 【Life Forms】(广义后缀自动机,线段树合并)
来个暴力广义后缀自动机做法(
对于这一堆字符串建立广义后缀自动机,在插入时对于每个节点标记上该节点代表的 \(endpos\) 中最长串是哪个串的前缀,显然若节点 \(x\) 在各个串出现的集合为 parent 树中节点 \(x\) 子树的标记的并集。
线段树合并统计答案?但是一共只有 \(n\leq 100\) 个串,所以可以对于每个节点暴力开一个 bitset \(col\) 存储出现集合,在树上合并之后若 \(|col_x|>\lfloor\frac{n}{2}\rfloor\) 则 \(maxlen_x\) 可能对答案有贡献。
输出方案的话,可以记录每个节点 \(endpos\) 集合中的一个元素 \((id,pos)\)(因为是多串,\(endpos\) 元素为二元组),则 \(maxlen\) 对应的串即为 \(s_{id}[pos-maxlen+1,pos]\) 。如何记录?只要在新建节点时记录即可,后面对 \(endpos\) 集合的操作只会向其中添加元素,原本存在于 \(endpos\) 中的元素不会消失。
按字典序大小输出目前没有什么好想法,不像后缀数组本身就是按照字典序大小,后缀自动机和字典序无太大关系。一个很蠢的做法:直接把满足条件的串扔进 vector 里,最后排序输出即可。
bitset 维护时空复杂度皆为 \(O(\dfrac{n|S|}{w}+|S|)\)。
线段树合并维护时空复杂度皆为 \(O(|S|\log n)\)。
因为懒,所以这里只有 bitset 维护的代码:
int kase,n;
int ans;
char s[105][1005];
struct sam{
int son[26],link,len;
int pos,id; //记录 endpos 的一个元素。
bitset<105> col; //维护出现集合。
}t[100005<<1];
int cntt=1;
inline int insert(int val,int las,int id,int pos){
if(t[las].son[val]){
int p=las,q1=t[p].son[val];
if(t[p].len+1==t[q1].len) return t[q1].col[id]=1,q1;
int q2=++cntt;
t[q2].len=t[p].len+1;
t[q2].id=id,t[q2].pos=pos;
t[q2].col[id]=1;
for(rg int i=0;i<26;++i) t[q2].son[i]=t[q1].son[i];
t[q2].link=t[q1].link;
t[q1].link=q2;
while(p&&t[p].son[val]==q1){
t[p].son[val]=q2;
p=t[p].link;
}
return q2;
}
int p=las,now=++cntt;
las=now;
t[now].len=t[p].len+1;
t[now].id=id,t[now].pos=pos;
t[now].col[id]=1;
while(p&&!t[p].son[val]){
t[p].son[val]=now;
p=t[p].link;
}
if(!p){
t[now].link=1;
}else{
int q1=t[p].son[val];
if(t[q1].len==t[p].len+1){
t[now].link=q1;
}else{
int q2=++cntt;
t[q2].len=t[p].len+1;
t[q2].id=id,t[q2].pos=pos;
for(rg int i=0;i<26;++i) t[q2].son[i]=t[q1].son[i];
t[q2].link=t[q1].link;
t[q1].link=t[now].link=q2;
while(p&&t[p].son[val]==q1){
t[p].son[val]=q2;
p=t[p].link;
}
}
}
return now;
}
vector<int> e[100005<<1];
vector<string> out;
void calc(int x){
for(int y:e[x]){
calc(y);
t[x].col|=t[y].col;
}
if(x>1&&t[x].col.count()>n/2) ans=max(ans,t[x].len);
}//统计答案。
inline void clear(){
for(rg int i=1;i<=cntt;++i){
memset(t[i].son,0,sizeof(t[i].son));
t[i].link=t[i].len=t[i].id=t[i].pos=0;
t[i].col.reset();
}
for(rg int i=1;i<=cntt;++i) e[i].clear();
cntt=1; ans=-1; out.clear();
}
signed main(){
//file();
while(scanf("%d",&n)!=EOF&&n){
if(++kase!=1) puts("");
for(rg int i=1;i<=n;++i){
scanf("%s",s[i]+1); const int len=strlen(s[i]+1);
int las=1;
for(rg int j=1;j<=len;++j) las=insert(s[i][j]-'a',las,i,j);
}
for(rg int i=2;i<=cntt;++i) e[t[i].link].emplace_back(i);
calc(1);
if(ans==-1){
puts("?"),clear(); continue;
}
for(rg int i=2;i<=cntt;++i){
if(t[i].col.count()>n/2&&t[i].len==ans){
string tmp;
for(rg int j=t[i].pos-t[i].len+1;j<=t[i].pos;++j) tmp.push_back(s[t[i].id][j]);
out.emplace_back(tmp);
}
}
sort(out.begin(),out.end());
for(string x:out){
cout<<x<<"\n";
}//输出方案。
clear();
}
return 0;
}