题解 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;
} 

留个二分 + 后缀数组的代码

posted @ 2022-02-09 16:37  Legitimity  阅读(30)  评论(0编辑  收藏  举报