Codeforces Round 953 Div.2 F 题解

连通块计数的一种常见思路是钦定代表元,但发现这题的连边方式并不好指定一个代表元,那么只能尝试优化建图。

我们尝试观察一下连边的情况,通过手玩样例获得一些几何直观的感受:

3 4 5
5 3 4
4 5 3

这个样例也许比较小,不过你真的把边画出来就会发现:连边形如 \(2n-1\) 条完整的斜线,中间零散地连了一些边。

其实很有道理,斜线上相邻两个元素距离为 \(2\),而 \(k\ge 2\),故能形成连边。

然后我们只需要考虑斜线之间的连边。根据曼哈顿距离转切比雪夫距离的原理,我们发现,两条斜线之间的距离就是它们的编号之差。(表述可能不清,假设从左下到右上编号依次为 \(1,2,\dots,2n-1\),那么 \(dis(i, j)=|i-j|)\))。

于是我们只需要考虑序列上的情况。这是一个相对经典的问题,对于每个质数,分别维护最后一次出现的位置 \(lst_v\),假设 \(v\)\(a_i\) 的因子,且 \(i-lst_v\le k\),连边 \((i,lst_v)\) 即可。时间复杂度 \(\mathcal O(n\log\log n)\)。不过我写的很粗糙,不是这个复杂度。

写完发现寄了,原因是没有考虑 \(1\) 没法形成斜线,因为 \(1\) 同样不会参与斜线之间的连边,直接特殊处理一下就行。

#include <bits/stdc++.h>
#define pb emplace_back
#define fir first
#define sec second
#define Tp template <typename T>

using i64 = long long;
using pii = std::pair<int, int>;

Tp T myabs(T x) { return x > 0 ? x : -x; }

constexpr int maxn = 1e6 + 5, N = 1e6;
int n, a[maxn << 1], k, pre[maxn << 1], lst[maxn];
std::vector<int> pr[maxn];

int find(int x) { return x == pre[x] ? x : pre[x] = find(pre[x]); }

void work() {
	std::cin >> n >> k;
	std::fill(a + 1, a + 2 * n, 0);
	for (int i = 1; i <= n; ++i) {
		std::cin >> a[n - 1 + i];
	}
	for (int i = 1; i < n; ++i) {
		a[n - i] = a[2 * n - i];
	}

	i64 ans = 0;
	for (int i = 1; i <= 2 * n - 1; ++i) {
		if (a[i] > 1) continue;
		ans += n - myabs(n - i) - 1;
	}

	std::iota(pre + 1, pre + 2 * n, 1);
	for (int i = 1; i <= 2 * n - 1; ++i) {
		for (auto& v : pr[a[i]]) {
			if (lst[v] && i - lst[v] <= k) {
				pre[find(i)] = find(lst[v]);
			}
			lst[v] = i;
		}
	}
	for (int i = 1; i <= 2 * n - 1; ++i) {
		for (auto& v : pr[a[i]]) {
			lst[v] = 0;
		}
	}

	int cnt = 0;
	for (int i = 1; i <= 2 * n - 1; ++i) {
		cnt += find(i) == i;
	}
	std::cout << (i64)cnt + ans << '\n';
	return;
}

int main() {
	std::cin.tie(nullptr)->sync_with_stdio(false);

	for (int i = 2; i <= N; ++i) {
		if (pr[i].empty()) {
			for (int j = i; j <= N; j += i) {
				pr[j].pb(i);
			}
		}
	}

	int t;
	std::cin >> t;
	while (t--) work();

	return 0;
}
posted @ 2024-06-16 22:31  ImALAS  阅读(97)  评论(0编辑  收藏  举报