BZOJ5137 [Usaco2017 Dec]Standing Out from the Herd

Standing Out from the Herd

定义一个字符串的「独特值」为只属于该字符串的本质不同的非空子串的个数。如 "amy" 与 “tommy” 两个串,只属于 "amy" 的本质不同的子串为 "a" "am" "amy" 共 3 个。只属于 "tommy" 的本质不同的子串为 "t" "to" "tom" "tomm" "tommy" "o" "om" "omm" "ommy" "mm" "mmy" 共 11 个。 所以 "amy" 的「独特值」为 3 ,"tommy" 的「独特值」为 11 。给定 N 个字符集为小写英文字母的字符串,所有字符串的长度和小于 105 ,求出每个字符串「独特值」

题解

参照bestfySovietPower的题解。

广义sam,right集合定义为多个串中出现的位置的并。

某个子串只出现在一个串中等价于当前状态的right集合只属于一个串。令f[u]表示u状态的right集合属于哪个串,若属于多个串记为-1,建出树以后自下而上合并right即可。

对每个串插入时新建的cur标记其属于哪个串。然后在parent树上DFS,合并子节点状态就行了。每个点的贡献就是len[i]−len[fa[i]]。

复杂度O(n)。


对于这题而言,写正规的SAM比较麻烦,可以直接使用错误做法——每次插入一个串的时候把last赋为1。

这样做在这道题里面是对的,考虑到如果本来有等价转移,那么就应该把它的状态标记成-1。而错误做法实际上是新建了一个废节点(没有节点能通过ch转移到它),但是这个废节点有fa连向q,所以在以后更新的时候q的状态最终是会被标记成-1的。

今后做题的时候应该想一下能不能用这种废节点机制简化代码。

co int N=2e5;
int last=1,tot=1;
int ch[N][26],fa[N],len[N],bl[N];
void extend(int c,int id){
	int p=last,cur=last=++tot;
	len[cur]=len[p]+1;
	for(;p&&!ch[p][c];p=fa[p]) ch[p][c]=cur;
	if(!p) fa[cur]=1;
	else{
		int q=ch[p][c];
		if(len[q]==len[p]+1) fa[cur]=q;
		else{
			int clone=++tot;
			memcpy(ch[clone],ch[q],sizeof ch[q]);
			fa[clone]=fa[q],len[clone]=len[p]+1;
			fa[cur]=fa[q]=clone;
			for(;ch[p][c]==q;p=fa[p]) ch[p][c]=clone;
		}
	}
	if(bl[cur]&&bl[cur]!=id) bl[cur]=-1;
	else bl[cur]=id;
}
vector<int> e[N];
int ans[N];
void dfs(int u){
	for(int i=0;i<e[u].size();++i) dfs(e[u][i]);
	if(bl[u]&&bl[u]!=-1) ans[bl[u]]+=len[u]-len[fa[u]];
	if(bl[fa[u]]&&bl[fa[u]]!=bl[u]) bl[fa[u]]=-1;
	else bl[fa[u]]=bl[u];
}
char str[N];
int main(){
	int n=read<int>();
	for(int i=1;i<=n;++i){
		scanf("%s",str+1);int l=strlen(str+1);
		last=1;
		for(int j=1;j<=l;++j) extend(str[j]-'a',i);
	}
	for(int i=2;i<=tot;++i) e[fa[i]].push_back(i);
	dfs(1);
	for(int i=1;i<=n;++i) printf("%d\n",ans[i]);
	return 0;
}

posted on 2019-05-22 15:07  autoint  阅读(132)  评论(0编辑  收藏  举报

导航