[Luogu P2408] 不同子串个数

题目链接

\(SA\)法棒,\(SA\)法妙,\(SA\)法做题呱呱叫(

Description

给定一个字符串,求它所有不重复的子串数量。

Solution

首先,要了解一点:

所有后缀的前缀的集合就是该字符串的子串集合。

对于一个子串来说,它的前缀个数就是它的长度,所以如果不考虑重复的话,该字符串的子串数量和就是 \(\frac{n(n + 1)}{2}\)

接下来考虑如何排除重复的情况。

那么可以发现,对于两个子串来说,它们重复的前缀数就是其最长公共前缀的长度。那不就是 \(SA\) 里面的 \(height\) 数组了吗?!

所以最后答案直接减去\(\sum_{i=1}^n height_{rk_i}\)

因此,

\[ Ans = \frac{n(n + 1)}{2} - \sum_{i=1}^n height_i \]

祭上 \(SA\) 大法就好啦~

其他的都是模板,具体是看看代码吧~

CODE

记得开 \(long\) \(long\) , 答案相乘时记得乘 \(1LL\)

不要问我是怎么知道的!问就是经验!


#include<cstdio>
#include<algorithm>
#include<cstring>
using namespace std;
#define ll long long
#define il inline
const int N = 1e6 + 5;
int n,m,sa[N],rk[N],height[N],cnt[N],x[N],y[N];
ll ans;
char s[N];
il void getsa() {
	int i,k,j,num;
	for(i = 1; i <= n; i ++) cnt[x[i] = s[i]] ++;
	for(i = 2; i <= m; i ++) cnt[i] += cnt[i - 1];
	for(i = n; i; i --) sa[cnt[x[i]]--] = i;
	for(k = 1; k <= n; k <<= 1) {
		num = 0;
		for(i = n - k + 1; i <= n; i ++) y[++num] = i;
		for(i = 1; i <= n; i ++) {
			if(sa[i] > k) y[++num] = sa[i] - k;
		}
		for(i = 1; i <= m; i ++) cnt[i] = 0;
		for(i = 1; i <= n; i ++) cnt[x[i]] ++;
		for(i = 2; i <= m; i ++) cnt[i] += cnt[i - 1];
		for(i = n; i; i --) sa[cnt[x[y[i]]]--] = y[i], y[i] = 0;
		swap(x,y);
		x[sa[1]] = num = 1;
		for(i = 2; i <= n; i ++) {
			x[sa[i]] = (y[sa[i]] == y[sa[i - 1]] && y[sa[i] + k] == y[sa[i - 1] + k]) ? num : ++num;
		}
		if(num == n) break;
		m = num;
	}
} 
il void get_height() {
	int i,j,k;
	for(i = 1; i <= n; i ++) rk[sa[i]] = i;
	for(i = 1, k = 0; i <= n; i ++) {
		if(rk[i] == 1) continue;
		if(k) k--;
		j = sa[rk[i] - 1];
		while(i + k <= n && j + k <= n && s[i + k] == s[j + k]) k ++;
		height[rk[i]] = k;
	}
}
int main() {
	scanf("%d",&n);
	scanf("%s",s + 1);
	m = 128;
	getsa();
	get_height();
	ans = 1ll * (n + 1) * n / 2;
	for(int i = 1; i <= n; i ++) ans -= height[i];
	printf("%lld\n",ans);
	return 0;
}

posted @ 2021-03-02 21:14  Spring-Araki  阅读(120)  评论(0编辑  收藏  举报