The 3rd Universal Cup. Stage 8: Cangqian

1|0C. Challenge NPC


考虑构造一个二分图,左边是1,3,5,7右侧是2,4,6,8。最优解肯定是一边全 1,一边全 2。

如果1,2之间不连边,这2就会被染色为 1,因此只要让2,3连边,3会被染色为2,然后1,4连边,4也会被染色为5,这时只要让2,54,5连边,5就会被染色为3。可以以此类推下去,这样左右的颜色都会依次是1,2,3,4,,因此只要让左右两侧都有k+2个点就好了

#include<bits/stdc++.h> using namespace std; using i32 = int32_t; using i64 = long long; i32 main() { ios::sync_with_stdio(false), cin.tie(nullptr); int k; cin >> k; int n = (k + 2) * 2, m = n * (n - 2) / 4, c = 2; cout << n << " " << m << " " << c << "\n"; for (int i = 1; i <= n; i++) cout << i % 2 + 1 << " "; cout << "\n"; for (int i = 1, t; i <= n; i++) { t = i + 2; if (i & 1) t++; else t--; for (; t <= n; t += 2) cout << i << " " << t << "\n"; } return 0; }

2|0E. Team Arrangement


假设我们知道分组的方案后,我们把组按照大小排序,然后贪心的选择r较小的即可。

然后考虑如果得到分组方案,因为n最大只有60,所以直接暴搜枚举出来就好了。

#include <bits/stdc++.h> using namespace std; using i32 = int32_t; using i64 = long long; #define int i64 using vi = vector<int>; using pii = pair<int, int>; const int inf = LLONG_MAX / 2; i32 main() { int n; cin >> n; vector<pii> a(n); for (auto &[l, r]: a) cin >> l >> r; ranges::sort(a); vi w(n + 1); for (int i = 1; i <= n; i++) cin >> w[i]; int res = -inf; vi p; auto dfs = [&](auto &self, int m, int x) -> void { if (m == 0) { priority_queue<int, vi, greater<>> heap; int i = 0, ok = 1, sum = 0; for (auto pi: p) { while (i < n and a[i].first <= pi) heap.push(a[i++].second); for (int j = 0; j < pi and ok; j++) { if (not heap.empty() and heap.top() >= pi) heap.pop(); else ok = 0; } if (ok == 0) break; sum += w[pi]; } if (ok) res = max(res, sum); return; } for (int y = x; y <= m; y++) { p.push_back(y); self(self, m - y, y); p.pop_back(); } }; dfs(dfs, n, 1); if (res == -inf) cout << "impossible\n"; else cout << res << "\n"; return 0; }

3|0F. Stage: Agausscrab


签到题

#include<bits/stdc++.h> using namespace std; using i32 = int32_t; using i64 = long long; const int mod = 1e9 + 7; i32 main() { ios::sync_with_stdio(false), cin.tie(nullptr); int n; cin >> n; vector<string> s(n); vector<int> a(n); for (int i = 0; i < n; i++) { cin >> s[i] >> a[i]; } string res; for (int i = 0, r; i < n; i++) { r = 1; for (int j = 0; j < n and r <= s.size(); j++) { if (a[j] > a[i]) r++; } while (not s[i].empty() and r > 0) s[i].pop_back(), r--; res += s[i]; } res[0] = res[0] - 'a' + 'A'; cout << "Stage: " << res; return 0; }

4|0J. Even or Odd Spanning Tree


首先,可以先求一个最小生成树出来。最小生成树一定是其中一个答案。

然后考虑另一个答案一定是最小生成树换一条边得到的。

下面简单的证明一下。

假设我们必须要换两条边,我们换之前的边是a,b换之后的是x,y,且a<b<x<y

则换边后的权值变化了x+yab

如果原始的边是一奇一偶,新边一定是同奇或同偶

如果原始的边是同奇或同偶,新边一定是一奇一偶

这样的话,一定可以保证只有一条边与原来的边奇偶不同,因此只换一条边也可满足奇偶变化。

现在这样的组合有ax,ay,bx,by

对于ax 权值的变化是 xa<x+yab,所以这种情况下换ax 更优

同理可以推出其他三种。

因此无论什么情况下,均匀换一条边更优的情况出现。

现在我们要考虑的就是选择那两条边。

考虑到,树上删掉任意一条边,均会对联通性发生改变,所以,新边一定和旧边在一个环上。

因此我们可以枚举所有不在最小生成树上的边,然后查找两个端点在树上路径中奇偶性不同且最大边一定是最优的。

查找最大边可以用 lca+树上倍增实现,复杂度O(logn),所以整体的复杂度就是O(mlogn)

#include<bits/stdc++.h> using namespace std; using i32 = int32_t; using i64 = long long; #define int i64 using edge = array<int, 3>; using pii = pair<int, int>; using vi = vector<int>; const int inf = LLONG_MAX / 2; class dsu { private: vector<int> fa; public: dsu(int n = 1) { fa = vector<int>(n + 1, -1), fa[0] = 0; } int getfa(int x) { if (fa[x] < 0) return x; return fa[x] = getfa(fa[x]); } void merge(int x, int y) { x = getfa(x), y = getfa(y); if (x == y) return; if (fa[x] > fa[y]) swap(x, y); fa[x] += fa[y], fa[y] = x; } bool same(int x, int y) { x = getfa(x), y = getfa(y); return (x == y); } }; void solve() { int n, m; cin >> n >> m; vector<edge> e(m); for (auto &[z, x, y]: e) cin >> x >> y >> z; ranges::sort(e); vector<vector<pii>> mst(n + 1); vector<edge> ne; dsu d(n); int sum = 0, cnt = 0; for (auto &[w, x, y]: e) { if (d.same(x, y)) ne.push_back({w, x, y}); else { sum += w, d.merge(x, y), cnt++; mst[x].emplace_back(y, w); mst[y].emplace_back(x, w); } } if (cnt != n - 1) { cout << "-1 -1\n"; return; } e = move(ne); int dn = 0, lg2N = log2(n); vector<int> dfn(n + 1), dep(n + 1); vector<vi> f(n + 1, vi(lg2N + 1)); vector<vi> fa(n + 1, vi(lg2N + 1)); vector edgeMax(2, vector(n + 1, vi(lg2N + 1))); for (int t = 0; t < 2; t++) { for (int i = 1; i <= n; i++) { for (int j = 0; j <= lg2N; j++) { if (edgeMax[t][i][j] == 0) continue; assert(edgeMax[t][i][j] % 2 == t); } } } auto dfs = [&](auto &&self, int x, int ff) -> void { f[dfn[x] = ++dn][0] = ff; for (auto [y, w]: mst[x]) { if (y == ff) continue; fa[y][0] = x, dep[y] = dep[x] + 1; edgeMax[w & 1][y][0] = w; self(self, y, x); } }; auto get = [&](int x, int y) -> int { if (dfn[x] < dfn[y]) return x; return y; }; auto lca = [&](int u, int v) -> int { if (u == v) return u; u = dfn[u], v = dfn[v]; if (u > v) swap(u, v); int d = log2(v - u++); return get(f[u][d], f[v - (1 << d) + 1][d]); }; dep[1] = 1; dfs(dfs, 1, 0); for (int i = 1; i <= lg2N; i++) for (int j = 1; j + (1 << i) - 1 <= n; j++) f[j][i] = get(f[j][i - 1], f[j + (1 << i - 1)][i - 1]); for (int i = 1; i <= lg2N; i++) for (int j = 1; j <= n; j++) { fa[j][i] = fa[fa[j][i - 1]][i - 1]; for (int t = 0; t < 2; t++) edgeMax[t][j][i] = max(edgeMax[t][j][i - 1], edgeMax[t][fa[j][i - 1]][i - 1]); } auto query = [&](int w, int x, int y) -> int { int z = lca(x, y), res = -1; for (int i = lg2N; i >= 0 and x != z; i--) { if (dep[fa[x][i]] < dep[z]) continue; res = max(res, edgeMax[w][x][i]); x = fa[x][i]; } for (int i = lg2N; i >= 0 and y != z; i--) { if (dep[fa[y][i]] < dep[z]) continue; res = max(res, edgeMax[w][y][i]); y = fa[y][i]; } return res; }; int res = inf; for (auto [w, x, y]: e) { auto t = query((w % 2) ^ 1, x, y); if (t <= 0) continue; res = min(res, sum - t + w); } if (res == inf) res = -1; if (sum & 1) cout << res << " " << sum << "\n"; else cout << sum << " " << res << "\n"; return; } i32 main() { ios::sync_with_stdio(false), cin.tie(nullptr); int T; cin >> T; while (T--) solve(); return 0; }

5|0M. Triangles


一个比较有意思的数学题。

首先要打表找规律,在不删除线的情况下,三角形的数目是n(n+2)(2n+1)8

然后考虑删掉横线在哪些三角形里面。

我们考虑顶点在上的情况。

我们可以枚举出底边的左端点i和长度x,枚举左端点[1,b]范围内。

然后右端点自然就是i+x,那么i+x要满足b+1i+xa

在考虑顶点,顶点是在ax行,那么0axa1

因此联立两个方程,可得max(1,bi+1)xmin(a,ai+1);

考虑顶点在下的情况,我们枚举右端点i长度x,类比可得到max(1,ib)xmin(na,i1)

满足上述条件的x均成立,因此我们可以O(1)的计算出每个i的贡献

#include<bits/stdc++.h> using namespace std; using i32 = int32_t; using i64 = long long; i32 main() { ios::sync_with_stdio(false), cin.tie(nullptr); i64 n, a, b; cin >> n >> a >> b; i64 sum = n * (n + 2) * (2 * n + 1) / 8; for (i64 i = 1; i <= b; i++) { i64 l = max(1ll, b - i + 1), r = min(a, a - i + 1); if (l <= r) sum -= r - l + 1; } for (i64 i = b + 1; i <= a + 1; i++) { i64 l = max(1ll, i - b), r = min(n - a, i - 1); if (l <= r) sum -= r - l + 1; } cout << sum; return 0; }

__EOF__

本文作者PHarr
本文链接https://www.cnblogs.com/PHarr/p/18408728.html
关于博主:前OIer,SMUer
版权声明CC BY-NC 4.0
声援博主:如果这篇文章对您有帮助,不妨给我点个赞
posted @   PHarr  阅读(137)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 震惊!C++程序真的从main开始吗?99%的程序员都答错了
· 【硬核科普】Trae如何「偷看」你的代码?零基础破解AI编程运行原理
· 单元测试从入门到精通
· 上周热点回顾(3.3-3.9)
· winform 绘制太阳,地球,月球 运作规律
历史上的今天:
2023-09-11 2022-2023 ACM-ICPC German Collegiate Programming Contest (GCPC 2022)
点击右上角即可分享
微信分享提示