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;
}