P8026 ONTAK2015 Bajtocja

发布时间 2023-07-18 15:12:59作者: dbxxx

P8026 ONTAK2015 Bajtocja

题目只考察连通性,不考察图更具体的结构,所以可以用 \(d\) 个并查集维护。然后就不会了。

观察题解后不难想到,两个点 \(u\)\(v\) 在图 \(i\) 上连通,当且仅当图 \(i\)\(u\)\(v\) 的并查集祖先相同。

因此,两个点 \(u\)\(v\)\(d\) 张图上都连通,等价于对于任意 \(i\)\(u\)\(v\) 在第 \(i\) 张图的并查集祖先相同。

所以考虑对每个点 \(u\) 维护一个长度为 \(d\) 的字符串 \(s_u\)\(s_u(i)\) 表示第 \(i\) 张图上 \(u\) 的并查集祖先。

于是 \(u\)\(v\)\(d\) 张图上连通等价于 \(s_u = s_v\)

我们动态地维护这 \(n\) 个字符串的哈希值,对于哈希值相同的 \(k\) 个字符串,可以给答案贡献 \(k^2\)。开桶记录即可。

因为桶的下标是字符串哈希值域量级,桶用 unordered_map(如果会哈希表用哈希表也行)。

现在考虑动态维护 \(n\) 个字符串哈希值的复杂度。很明显,每次我们更改的字符串不能太多。

对于一次 \((d, a, b)\) 的操作,我们会修改 \(s_u\) 的第 \(d\) 个字符,其中 \(u\) 是在这次连边中被更改祖先的点数。我们要控制这个点数的量级。

所以不路径压缩,考虑按 siz 合并。这样以来,一个点在第 \(d\) 张图中,被更改祖先的次数不会超过 \(\log n\) 次(因为被更改一次祖先,它所在的并查集树大小就会翻倍),每个字符串被修改的数量不超过 \(d\log n\),所有字符串被修改的总次数为 \(dn\log n\) 量级。

时间复杂度 \(\Theta(dn\log n)\),常数略大。

typedef unsigned long long ull;

const int D = 205;
const int N = (int)5e3 + 5;

int rt[D][N];
std :: vector <int> t[D][N];
ull ha[N], val[D];
int ans;
std :: unordered_map <ull, int> cnt;

inline void add(ull ha) {
	int x = cnt[ha];
	ans += 2 * x + 1;
	++cnt[ha];
}

inline void del(ull ha) {
	int x = cnt[ha];
	ans -= 2 * x - 1;
	--cnt[ha];
}

int main() {
	int d = read(), n = read(), m = read();
	std :: mt19937_64 rng(std :: random_device{}());
	for (int i = 1; i <= d; ++i)
		val[i] = rng();
	for (int i = 1; i <= d; ++i) {
		for (int u = 1; u <= n; ++u) {
			rt[i][u] = u;
			ha[u] += u * val[i];
			t[i][u].push_back(u);
		}
	}

	for (int u = 1; u <= n; ++u)
		add(ha[u]);
	
	while (m--) {
		int u = read(), v = read(), k = read();
		u = rt[k][u]; v = rt[k][v];
		if (u == v) {
			printf("%d\n", ans);
			continue;
		}
		if (t[k][v].size() > t[k][u].size())
			std :: swap(u, v);
		for (int x : t[k][v]) {
			t[k][u].push_back(x);
			rt[k][x] = u;
			del(ha[x]);
			ha[x] += (u - v) * val[k];
			add(ha[x]);
		}
		t[k][v].clear();
		printf("%d\n", ans);
	}
	return 0;
}

如果您是从洛谷题解过来的,觉得我的题解写的不错,解决了您的疑惑的话,请回到洛谷题解区给我题解点个赞,谢谢!