2018-2019 ACM-ICPC, Asia Xuzhou Regional Contest


A. Rikka with Minimum Spanning Trees

题目很长,其实就是按照他给出的代码生成边,然后求最小生成树,注意判断不连通的情况。

点击查看代码
#include <bits/stdc++.h>

using i64 = long long;
using ui64 = unsigned long long;

int fa[100010];
const int mod = 1e9 + 7;

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

void solve() {
	int n, m;
	ui64 k1, k2;
	std::cin >> n >> m >> k1 >> k2;
	auto f = [&]() -> ui64 {
		ui64 k3 = k1, k4 = k2;
		k1 = k4;
		k3 ^= k3 << 23;
		k2 = k3 ^ k4 ^ (k3 >> 17) ^ (k4 >> 26);
		return k2 + k4;
	};

	for (int i = 1; i <= n; ++ i) {
		fa[i] = i;
	}

	std::vector<std::tuple<ui64, int, int>> edges;
	for (int i = 1; i <= m; ++ i) {
		int u = f() % n + 1;
		int v = f() % n + 1;
		ui64 w = f();
		edges.emplace_back(w, u, v);
	}


	std::ranges::sort(edges);
	int ans = 0;
	for (auto & [w, u, v] : edges) {
		if (find(u) == find(v)) {
			continue;
		}

		ans = (ans + w % mod) % mod;
		fa[find(v)] = find(u);
	}

	int cnt = 0;
	for (int i = 1; i <= n; ++ i) {
		cnt += find(i) == i;
	}

	if (cnt > 1) {
		ans = 0;
	}
	std::cout << ans << "\n";
}

int main() {
	std::ios::sync_with_stdio(false), std::cin.tie(0), std::cout.tie(0);
	int t = 1;
	std::cin >> t;
	while (t -- ) {
		solve();
	}
	return 0;
}

G. Rikka with Intersections of Paths

题意:给你一棵树和\(m\)条路径,从中选出\(k\)条使得这\(k\)条路径都至少有一个交点的方案数是多少?

考虑在路径的\(lca\)处求,记\(cnt[u]\)\(u\)作为\(lca\)的次数,然后树上差分一下,求出\(sum[u]\)为从下面经过\(u\)且延伸到上面的路径的数。
那么可以全选子树的路径,也可以选一些子树的路径选一些延伸到上面的路径,但不能全选延伸到上面的路径,因为这些路径在它们的\(lca\)处会被当作子树路径计算。那么就是\(\sum_{i=1}^{cnt[u]} C(cnt[u], i) \times C(sum[u], k - i)\)。预处理组合数后直接暴力算这个式子就行,因为所有\(cnt[u]\)的总和就是\(m\)

点击查看代码
#include <bits/stdc++.h>

using i64 = long long;
using ui64 = unsigned long long;

const int mod = 1e9 + 7;
const int N = 3e5 + 5;

int fact[2 * N], infact[2 * N];

int power(int a, int b) {
	int res = 1;
	for (;b;b >>= 1, a = (i64)a * a % mod) {
		if (b & 1) {
			res = (i64)res * a % mod;
		}
	}
	return res;
}

void init(int n) {
	fact[0] = infact[0] = 1;
	for (int i = 1; i <= n; ++ i) {
		fact[i] = (i64)i * fact[i - 1] % mod;
	}

	infact[n] = power(fact[n], mod - 2);
	for (int i = n; i > 1; -- i) {
		infact[i - 1] = (i64)infact[i] * i % mod;
	}
}

int C(int n, int m) {
	if (n < m || m < 0) {
		return 0;
	}

	return (i64)fact[n] * infact[m] % mod * infact[n - m] % mod;
}

void solve() {
	int n, m, k;
	std::cin >> n >> m >> k;
	std::vector<std::vector<int>> adj(n + 1);
	for (int i = 1; i < n; ++ i) {
		int u, v;
		std::cin >> u >> v;
		adj[u].push_back(v);
		adj[v].push_back(u);
	}

	int lg = std::__lg(n) + 1;
	std::vector f(n + 1, std::vector<int>(lg + 1));
	std::vector<int> d(n + 1);
	auto dfs = [&](auto & self, int u, int fa) -> void {
		for (auto & v : adj[u]) {
			if (v == fa) {
				continue;
			}

			d[v] = d[u] + 1;
			f[v][0] = u;
			for (int i = 1; i <= lg; ++ i) {
				f[v][i] = f[f[v][i - 1]][i - 1];
			}
			self(self, v, u);
		}
	};

	d[1] = 1;
	dfs(dfs, 1, 0);
	auto lca = [&](int x, int y) -> int {
		if (d[x] < d[y]) {
			std::swap(x, y);
		}

		for (int i = lg; i >= 0; -- i) {
			if (d[f[x][i]] >= d[y]) {
				x = f[x][i];
			}
		}

		if (x == y) {
			return x;
		}

		for (int i = lg; i >= 0; -- i) {
			if (f[x][i] != f[y][i]) {
				x = f[x][i];
				y = f[y][i];
			}
		}

		return f[x][0];
	};

	std::vector<int> cnt(n + 1), sum(n + 1);
	for (int i = 0; i < m; ++ i) {
		int u, v;
		std::cin >> u >> v;
		if (d[u] > d[v]) {
			std::swap(u, v);
		}

		int x = lca(u, v);
		cnt[x] += 1;
		sum[x] -= 2;
		sum[u] += 1;
		sum[v] += 1;
	}

	int ans = 0;
	auto dfs1 = [&](auto & self, int u, int fa) -> void {
		for (auto & v : adj[u]) {
			if (v == fa) {
				continue;
			}

			self(self, v, u);
			sum[u] += sum[v];
		}

		for (int i = 1; i <= cnt[u]; ++ i) {
			ans = (ans + (i64)C(cnt[u], i) * C(sum[u], k - i) % mod) % mod;
		}
	};

	dfs1(dfs1, 1, 0);
	std::cout << ans << "\n";
}

int main() {
	std::ios::sync_with_stdio(false), std::cin.tie(0), std::cout.tie(0);
	int t = 1;
	init(6e5);
	std::cin >> t;
	while (t -- ) {
		solve();
	}
	return 0;
}

H. Rikka with A Long Colour Palette

题意:\(n\)个区间,\(k\)种颜色,你可以给每个区间填色。使得所有拥有\(k\)种颜色的最大连续子区间的长度减一的和最大。求这个最大和以及方案。最大连续子区间是指\([l, r]\)使得区间所有位置都有\(k\)种颜色,但\(l - 1, r + 1\)都没有\(k\)种颜色,区间的价值就是\(r-l\)

考虑按左端点排序后贪心。
如果我们给当前区间填色,那么这个颜色一直到\(r_i\)都是有效的,与它相交的应该填其它颜色,于是我们用一个优先队列模拟,把填\([1, k]\)种颜色的上一个区间的右端点存下,一开始全部入队\((0, i)\)。然后从左到右填色就行,每次取队顶颜色给当前区间填。
然后就是方案数,就是一个差分,把左右端点拆开贡献,然后排序。

点击查看代码
#include <bits/stdc++.h>

using i64 = long long;

void solve() {
	int n, k;
	std::cin >> n >> k;
	std::vector<std::tuple<int, int, int>> a(n);
	for (int i = 0; i < n; ++ i) {
		int l, r;
		std::cin >> l >> r;
		a[i] = {l, r, i};
	}

	std::ranges::sort(a);
	using PII = std::pair<int, int>;
	std::priority_queue<PII, std::vector<PII>, std::greater<>> heap;
	for (int i = 1; i <= k; ++ i) {
		heap.emplace(0, i);
	}

	std::vector<int> ans(n);
	for (auto & [l, r, id] : a) {
		int x = heap.top().second; heap.pop();
		ans[id] = x;
		heap.emplace(r, x);
	}

	std::vector<std::tuple<int, int, int>> b;
	for (int i = 0; i < n; ++ i) {
		auto [l, r, id] = a[i];
		b.emplace_back(l, ans[id], 1);
		b.emplace_back(r, ans[id], -1);
	}

	std::ranges::sort(b);

	std::vector<int> cnt(k + 1);
	int sum = 0, tot = 0;
	for (int i = 0; i < 2 * n; ++ i) {
		auto [p, c, w] = b[i];
		if (cnt[c]) {
			-- tot;
		}

		cnt[c] += w;
		if (cnt[c]) {
			++ tot;
		}

		if (tot == k) {
			sum += std::get<0>(b[i + 1]) - p;
		}
	}

	std::cout << sum << "\n";
	for (int i = 0; i < n; ++ i) {
		std::cout << ans[i] << " \n"[i == n - 1];
	}
}

int main() {
	std::ios::sync_with_stdio(false), std::cin.tie(0), std::cout.tie(0);
	int t = 1;
	std::cin >> t;
	while (t -- ) {
		solve();
	}
	return 0;
}

I. Rikka with Sorting Networks

题意:有\(k\)个比较器,依次执行下去,第\(i\)个比较器是\(u_i, v_i\),如果\(a_{u_i} > a_{v_i}\),则交换\(a_{u_i}, a_{v_i}\)。求有多少排列执行操作后最长上升子序列长度大于等于\(n-1\)\(n\leq 50, k\leq 10\)

反着想,有多少排列的最长上升子序列长度大于等于\(n-1\)?长度为\(n\)的只有\({1, 2, ..., n - 1, n}\)。长度为\(n-1\)的则是把\(i\)拿出,然后剩下的数有\(n+1\)的空,除了原来的位置不能插,还剩下\(n\)个空,然后相邻两个会重复一个排列一次,所有一共有\(1 + n * n - n = n*(n-1) + 1\)个。那么我们构造出这样的所有排列,考虑反着操作,如果\(a_{u_i} < a_{v_i}\),要么是经过\(i\)操作后有序了,要么是本来有序,直接爆搜即可。时间复杂度\(O(n^22^k)\)

点击查看代码
#include <bits/stdc++.h>

using i64 = long long;

void solve() {
	int n, k, q;
	std::cin >> n >> k >> q;
	std::vector<int> a(k + 1), b(k + 1);
	for (int i = 1; i <= k; ++ i) {
		std::cin >> a[i] >> b[i];
	}

	std::vector<int> p(n + 1);
	std::ranges::iota(p, 0);

	int ans = 0;
	auto dfs = [&](auto & self, int u, int add) -> void {
		if (u == 0) {
			ans = (ans + add) % q;
			return;
		}

		if (p[a[u]] < p[b[u]]) {
			self(self, u - 1, add);
			std::swap(p[a[u]], p[b[u]]);
			self(self, u - 1, add);
			std::swap(p[a[u]], p[b[u]]);
		}
	};

	dfs(dfs, k, 1);
	for (int i = 1; i <= n; ++ i) {
		for (int j = i; j < n; ++ j) {
			std::swap(p[j], p[j + 1]);
			dfs(dfs, k, 1);
		}

		for (int j = n; j > i; -- j) {
			std::swap(p[j], p[j - 1]);
		}

		for (int j = i; j > 1; -- j) {
			std::swap(p[j], p[j - 1]);
			dfs(dfs, k, 1);
		}

		for (int j = 1; j < i; ++ j) {
			std::swap(p[j], p[j + 1]);
		}
	}

	for (int i = 1; i < n; ++ i) {
		std::swap(p[i], p[i + 1]);
		dfs(dfs, k, -1);
		std::swap(p[i], p[i + 1]);
	}

	std::cout << ans << "\n";
}

int main() {
	std::ios::sync_with_stdio(false), std::cin.tie(0), std::cout.tie(0);
	int t = 1;
	std::cin >> t;
	while (t -- ) {
		solve();
	}
	return 0;
}

M. Rikka with Illuminations

题意:给一个凸多边形和\(m\)个在外面的点,每个点是一个光源,求最少选几个点可以照亮凸多边形的所有边。

可以发现一个点能照亮的边是连续的,那么可以把这样边映射为点,那么就是一个长度为\(n\)的线段,考虑本来是一个换,一个点照亮的边可能跨越了左右端点,那么就把线段复制一份,这样就可以把每个点照亮的边映射为一个区间了。然后枚举环上的起点,重新破环为链,问题就变成了选最少的区间覆盖整条线段。

点击查看代码
#include <bits/stdc++.h>

using i64 = long long;

void solve() {
	int n, m;
	std::cin >> n >> m;
	std::vector<std::pair<int, int>> a(n), b(m);
	for (int i = 0; i < n; ++ i) {
		std::cin >> a[i].first >> a[i].second;
	}

	for (int i = 0; i < m; ++ i) {
		std::cin >> b[i].first >> b[i].second;
	}

	using PII = std::pair<i64, i64>;
	auto check = [&](const PII & a, const PII & b, const PII & c) -> bool {
		PII ab = {b.first - a.first, b.second - a.second};
		PII ac = {c.first - a.first, c.second - a.second};
		return ab.first * ac.second - ab.second * ac.first < 0;
	};

	a.push_back(a[0]);
	std::vector<std::array<int, 3>> c;
	for (int i = 0; i < m; ++ i) {
		std::vector<int> d;
		for (int j = 0; j < n; ++ j) {
			if (check(a[j], a[j + 1], b[i])) {
				d.push_back(j);
			}
		}

		int l = d[0], r = d.back();
		for (int j = 1; j < d.size(); ++ j) {
			if (d[j] - d[j - 1] > 1) {
				l = d[j], r = d[j - 1] + n;
				break;
			}
		}

		c.push_back(std::array<int, 3>{l, r, i});
	}

	std::ranges::sort(c);
	auto get = [&](int L, int R) -> std::vector<int> {
		std::vector<int> res;
		int last = L - 1;
		for (int i = 0; i < m; ++ i) {
			if (c[i][0] > last + 1) {
				return {};
			}

			int j = i;
			int now = c[i][1], maxid = c[i][2];
			while (j < m && c[j][0] <= last + 1) {
				if (now < c[j][1]) {
					now = c[j][1];
					maxid = c[j][2];
				}
				++ j;
			}

			res.push_back(maxid);
			if (now >= R) {
				return res;
			}

			last = now;
			i = j - 1;
		}

		return {};
	};

	std::vector<int> ans;
	for (int i = 0; i < n; ++ i) {
		auto res = get(i, i + n - 1);
		if (res.empty()) {
			continue;
		}

		if (ans.empty() || res.size() < ans.size()) {
			ans = res;
		}
	}

	if (ans.empty()) {
		std::cout << -1 << "\n";
	} else {
		std::cout << ans.size() << "\n";
		for (auto & i : ans) {
			std::cout << i + 1 << " \n"[i == ans.back()];
		}
	}
}

int main() {
	std::ios::sync_with_stdio(false), std::cin.tie(0), std::cout.tie(0);
	int t = 1;
	std::cin >> t;
	while (t -- ) {
		solve();
	}
	return 0;
}
posted @ 2025-07-14 19:54  maburb  阅读(40)  评论(0)    收藏  举报