「REOI-1」渺茫的希望(SAM,构造)

感叹出题人的强大。

Description

给定一个字符串 \(S\) ,让你求一个最小生成树总权值和,点集为所有本质不同子串,边权为两串出现次数之和 + 两串 \(\text{LCP}\)

\(|S| \leq 10 ^ 5,\ |\Sigma| \leq 26\)

Analysis

这个本质不同子串第一反应就是 \(\text{SAM}\) ,但是这个范围让我以为是一个重工程,然后就一直在考虑怎么优化建图(

(后来才反应过来本质不同子串那道题的范围也是这个)

其实可以直接构造。

考虑到边权是由两部分组成的,出现次数是不可避免的,所以可以考虑减少 \(\text{LCP}\) 带来的贡献。

感性上说,我们想要同时保证一些出现次数比较多的子串度数尽量小,还要求 \(\text{LCP}\) 总数量最少。

反过来的话,就可以直接让一些出现次数为 \(1\) 的点的度数尽量大,并且还能从第一个字符开始杜绝 \(\text{LCP}\) 的产生。

Solution

上述那样的点我们是找得到的:

\(t_i\) 表示从第一个字符 \(i\) 出现的地方开始的后缀,这种子串的出现次数必定为 \(1\) ,因为前面没有 \(i\) 了,所以不可能再有长度和 \(t_i\) 一样并且还是 \(i\) 开头的子串了。

然后我们想从源头杜绝 \(\text{LCP}\) 的产生,直接随便选择一个 \(t_i\) 连向所有不是 \(i\) 开头的子串,然后剩下还会有一些 \(i\) 开头的子串,直接类似的在挑一个 \(t_j\) 全部连上。

从理性分析上,两种贡献都已经压到了最小了,那答案到底是多少呢?

因为可以明显感觉到,所有的边两端点都是形如 \(t_i\)\(s\) 的。

所以对于左边,总答案就是除了 \(t_i\) 以外所有子串总出现次数,当然也就是 \(S\) 总子串个数 \(- 1\)

对于右边,每次都是 \(1\) ,所以总和就是本质不同子串个数 \(- 1\),假定为 \(num - 1\)

对于 \(\text{LCP}\) ,它的贡献是 \(0\)

两边综合起来,大概就是:

\(\Big(\frac{n (n + 1)}{2} - 1\Big) + (num - 1)\)

其实这是有问题的,因为前面我们直接强行钦定了两个字符,显然对于全串只有一个字符的情况是不适用的。

怎么办呢,我们还是按照前面贪心的思路:

\(\text{LCP}\) 的贡献是不可避免的了,所以我们要写一下每一种边的权值:

对于一个长度为 \(a\) 另一个长度为 \(b\) 的子串连成的边(钦定 \(a \leq b\) ):

\(w = (n - a + 1) + (n - b + 1) + a = 2n - b + 2\)

所以只需要 \(\sum b\) 最大就行,那就构造成以全串为菊花心的菊花图就行了,答案就是上面那个东西,带入 \(b = n\) ,乘上边数 \(n\) 就行了。

\((n + 2)(n - 1)\)

总体只需要求本质不同子串个数就行了,时间复杂度 \(O(n \log |\Sigma|)\)

再次感叹出题人的强大。

Code

Code

/*

*/
#include 
using namespace std;

#define File(a) freopen(a".in", "r", stdin), freopen(a".out", "w", stdout);
#define Check(a) freopen(a".in", "r", stdin), freopen(a".ans", "w", stdout);

typedef long long ll;
typedef unsigned long long ull;
typedef std::pair pii;
#define fi first
#define se second
#define mp std::make_pair

const int mod = 998244353;
template 
inline int M(A x) {return x;}
template 
inline int M(A x, B ... args) {return 1ll * x * M(args...) % mod;}

#define mi(x) (x >= mod) && (x -= mod)
#define ad(x) (x < 0) && (x += mod)

const int N = 2e5 + 10;

int n;
char a[N];

struct SAM {
	int cnt, las, len[N], link[N], ch[N][26];
	int tong[N], rk[N]; ll ans;

	inline void init() {cnt = las = 1; memset(ch[1], 0, sizeof(ch[1]));}

	inline void SAN_stru(int c) {
		int cur = ++cnt, p = las;
		memset(ch[cur], 0, sizeof(ch[cur]));
		las = cur;
		len[cur] = len[p] + 1;
		while (p && !ch[p][c]) ch[p][c] = cur, p = link[p];

		if (!p) return void(link[cur] = 1);
		
		int q = ch[p][c];
		if (len[q] == len[p] + 1) return void(link[cur] = q);
		
		int clo = ++cnt;
		link[clo] = link[q]; len[clo] = len[p] + 1;
		link[q] = link[cur] = clo;
		memcpy(ch[clo], ch[q], sizeof(ch[q]));
		while (p && ch[p][c] == q) ch[p][c] = clo, p = link[p];
	}

	inline void Tong_sort() {
		for (int i = 1; i <= cnt; ++i) tong[i] = 0;

		for (int i = 1; i <= cnt; ++i) ++tong[len[i]];
		for (int i = 1; i <= cnt; ++i) tong[i] += tong[i - 1];
		for (int i = 1; i <= cnt; ++i) rk[tong[len[i]]--] = i;

		for (int i = cnt, v; i >= 1; --i) {
			v = rk[i]; ans += len[v] - len[link[v]];
		}

		ans += 1ll * n * (n + 1) / 2 - 2;
		std::cout << ans << '\n';
	}
} s;

inline bool pd() {
	for (int i = 1; i < n; ++i) {
		if (a[i] != a[i - 1]) return 0;
	}
	
	std::cout << 1ll * (n + 2) * (n - 1) << "\n";
	return 1;
}

int main() {
	std::ios::sync_with_stdio(false);
	std::cin.tie(nullptr);
	
	std::cin >> n >> a;
	if (pd()) return 0;
	s.init();
	for (int i = 0; i < n; ++i) s.SAN_stru(a[i] - 'a');
	s.Tong_sort();

	return 0;
}

posted @ 2022-08-07 22:12  Illusory_dimes  阅读(55)  评论(0编辑  收藏  举报