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 ,求出每个字符串「独特值」
题解
参照bestfy和SovietPower的题解。
广义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;
}
静渊以有谋,疏通而知事。