4566: [Haoi2016]找相同字符
Time Limit: 20 Sec Memory Limit: 256 MB
Submit: 861 Solved: 495
[Submit][Status][Discuss]
Description
Input
两行,两个字符串s1,s2,长度分别为n1,n2。1 <=n1, n2<= 200000,字符串中只有小写字母
Output
输出一个整数表示答案
Sample Input
aabb
bbaa
Sample Output
10
HINT
题解:
第一眼看题,跟子串有关不是Sam就是Sa (雾)
第二眼看题,不就是个广义后缀自动机,邪魅一笑的发现这题很水啊
先来看看如何用sam求一个串的不同子串
建好sam后,一个结点所代表的子串是根节点到它的各个路径,然后路径数量刚好是dis[i] -dis[fa[i]];
设sam里有dt个结点,则一个串的不同子串为∑(dis[i] - dis[fa[i]]), i ∈[1,dt]
根据sam的特点,一个点出现了,那么必然它的父亲也出现了,因为它的父亲是它的后缀。
那么对于每一次sam出来的点,我们使它权值w[i]++,然后按dis基数排序后,倒着扫一遍w[fa[i]] += w[i];
那么对于每个子串,假设它对应结点为i,那么它出现次数为 w[i]
这样我们就利用sam的性质对于一个串求出了它不同子串数量,或者某个子串出现次数。
再来看看广义后缀自动机。看看这篇博文http://blog.csdn.net/wangzhen_yu/article/details/45481269
看不懂没关系,简单解释就是按一个串建完sam后,后面的每个串的last回到根节点,又继续建,不清空之前串的记录。具体看代码
然后对于每个点,我们就可以发现,它可以代表第一个字符串子串,也可以代表第二个字符串子串。
所以我们按一个串的方法,求出两个字符串对应的w数组,那么每个点i就有w[i][0] 和 w[i][1];
这个点所代表的dis[fa[i]] - dis[i]个在第一个串的子串都出现了w[i][0] 次
这个点所代表的dis[fa[i]] - dis[i]个在第二个串的子串都出现了w[i][1] 次
那么这个点所产生的贡献就是 (dis[fa[i]] - dis[i]) * w[i][0] * w[i][1];
最后就发现答案是 ∑ (dis[fa[i]] - dis[i]) * w[i][0] * w[i][1] , i ∈[1,dt]
需要注意的是因为用的是基数排序,相同dis的点相对先后顺序是不变的,所以如果一次sam加了两个点,要使代表当前last的点出现位置靠后,具体见代码
AC代码:
# include <cstdio> # include <cstring> # include <iostream> using namespace std; typedef long long LL; const int N = 220012 << 2; int ch[N][26],fa[N],dis[N],dt = 1,que[N],l,ls,bac[N]; char str[N];LL ans,sz[N][2]; int Sam(int last,int c) { int u = last,np; while(u && !ch[u][c])que[++que[0]] = u,u = fa[u]; if(!u) { np = ++dt;dis[np] = dis[last] + 1;fa[np] = 1; while(que[0])ch[que[que[0]--]][c] = np; } else { int v = ch[u][c]; if(dis[v] == dis[u] + 1) { np = ++dt;dis[np] = dis[last] + 1;fa[np] = v; while(que[0])ch[que[que[0]--]][c] = np; } else { int av = ++dt;np = ++dt; dis[np] = dis[last] + 1; while(que[0])ch[que[que[0]--]][c] = np; dis[av] = dis[u] + 1; memcpy(ch[av],ch[v],sizeof ch[v]); fa[av] = fa[v];fa[v] = fa[np] = av; while(u && ch[u][c] == v)ch[u][c] = av,u = fa[u]; } } return np; } int main() { scanf("%s",str);l = strlen(str);ls = 1; for(int i = 0;i < l;i++)ls = Sam(ls,str[i] - 'a'),sz[ls][0]++; scanf("%s",str);l = strlen(str);ls = 1; for(int i = 0;i < l;i++)ls = Sam(ls,str[i] - 'a'),sz[ls][1]++; for(int i = 1;i <= dt;i++)bac[dis[i]]++; for(int i = 1;i < N;i++)bac[i] += bac[i - 1]; for(int i = dt;i >= 1;i--)que[--bac[dis[i]]] = i; for(int i = dt;i >= 1;i--)sz[fa[que[i]]][1] += sz[que[i]][1],sz[fa[que[i]]][0] += sz[que[i]][0]; for(int i = 1;i <= dt;i++)ans += 1LL * (dis[i] - dis[fa[i]]) * (sz[i][0] * sz[i][1]); printf("%lld\n",ans); }