The 2023 ICPC Asia Macau Regional Contest

1|0A. (-1,1)-Sumplete


首先只取1,这样的话选1和不选-1产生的贡献就是都是 +1。

枚举每一行,然后贪心选择需求最大的若干列。

#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 i32 inf = INT_MAX / 2; i32 main() { ios::sync_with_stdio(false), cin.tie(nullptr); int n; cin >> n; vector a(n, vi(n, 1)); string s; for (int i = 0; i < n; i++) { cin >> s; for (int j = 0; j < n; j++) if (s[j] == '-') a[i][j] = -1; } vi row(n), col(n); for (auto &i: row) cin >> i; for (auto &i: col) cin >> i; priority_queue<pii> heap; for (int j = 0; j < n; j++) { int need = col[j]; for (int i = 0; i < n; i++) if (a[i][j] < 0) need++; if (need > 0) heap.emplace(need, j); } vector b(n, vi(n, -1)); for (int i = 0; i < n; i++) { int t = row[i]; for (int j = 0; j < n; j++) if (a[i][j] < 0) t++; if (t > heap.size()) { cout << "No\n"; return 0; } vector<pii> cur; for (; t; t--) cur.push_back(heap.top()), heap.pop(); for (auto [need, j]: cur) { need--, b[i][j] = 1; if (need > 0) heap.emplace(need, j); } } if (not heap.empty()) { cout << "No\n"; return 0; } cout << "Yes\n"; for (int i = 0; i < n; i++) { for (int j = 0; j < n; j++) cout << (a[i][j] == b[i][j]); cout << "\n"; } return 0; }

2|0D. Graph of Maximum Degree 3


对于一个点为x的图,最多只有3x2条边,如果只用红色边联通最少需要x1条边,如果只用蓝色边联通最少也需要x1条边。因此符合条件的图一定满足

3x22x2

因此符合条件的图点数不超过4。此时我们就可以分别建图 ,然后枚举出联通块,然后找红色联通块和蓝色连通块相同的个数。

#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 i32 inf = INT_MAX / 2; const int mod = 998244353; i32 main() { ios::sync_with_stdio(false), cin.tie(nullptr); int n, m; cin >> n >> m; vector<vector<pii>> e(n + 1); for (int x, y, c; m; m--) { cin >> x >> y >> c; e[x].emplace_back(y, c); e[y].emplace_back(x, c); } set<vi> R, B; for (int x = 1; x <= n; x++) { for (auto [y, c1]: e[x]) { if (y > x) { if (c1) R.insert({x, y}); else B.insert({x, y}); } for (auto [z, c2]: e[x]) { if (y == z) continue; if (y < z) continue; if (c1 != c2) continue; vi t = {x, y, z}; ranges::sort(t); if (c1) R.insert(t); else B.insert(t); for (auto [w, c3]: e[y]) { if (w == x or w == y or w == z) continue; if (c1 != c3) continue; t = {w, x, y, z}; ranges::sort(t); if (c1) R.insert(t); else B.insert(t); } for (auto [w, c3]: e[z]) { if (w == x or w == y or w == z) continue; if (c1 != c3) continue; t = {w, x, y, z}; ranges::sort(t); if (c1) R.insert(t); else B.insert(t); } } } } int res = n; for (auto it: R) if (B.count(it)) res++; cout << res % mod; return 0; }

3|0E. Inverse Topological Sort


我们观察拓扑序,为什么会出现逆序?一种最简单的情况一定时逆序对之间出现了边。

但是如果所有的逆序对之间都建边,边的数量可能很大,我们考虑对于每个点,先它前面最近的逆序对两边,这样的话,边数不会超过2n

当然了建出来图也不一定就满足条件,我们还要在求一下拓扑序。

#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 i32 inf = INT_MAX / 2; const int mod = 998244353; i32 main() { ios::sync_with_stdio(false), cin.tie(nullptr); int n; cin >> n; vector<vi> e(n); vi a(n), b(n); stack<int> stk; for (int i = 0; i < n; i++) { cin >> a[i], a[i]--; while (not stk.empty() and stk.top() < a[i]) stk.pop(); if (not stk.empty()) e[stk.top()].push_back(a[i]); stk.push(a[i]); } while (not stk.empty()) stk.pop(); for (int i = 0; i < n; i++) { cin >> b[i], b[i]--; while (not stk.empty() and stk.top() > b[i]) stk.pop(); if (not stk.empty()) e[stk.top()].push_back(b[i]); stk.push(b[i]); } vi c, d; vi inDegC(n); for (int x = 0; x < n; x++) for (auto y: e[x]) inDegC[y]++; vi inDegD = inDegC; priority_queue<int, vi, greater<>> heapC; priority_queue<int> heapD; for (int i = 0; i < n; i++) if (inDegC[i] == 0) heapC.push(i), heapD.push(i); while (not heapC.empty()) { auto x = heapC.top(); heapC.pop(); c.push_back(x); for (auto y: e[x]) if (--inDegC[y] == 0) heapC.push(y); } while (not heapD.empty()) { auto x = heapD.top(); heapD.pop(); d.push_back(x); for (auto y: e[x]) if (--inDegD[y] == 0) heapD.push(y); } if (a != c or b != d) { cout << "NO"; return 0; } cout << "YES\n"; vector<pii> ret; for (int x = 0; x < n; x++) for (auto y: e[x]) ret.emplace_back(x, y); cout << ret.size() << "\n"; for (auto [x, y]: ret) cout << x + 1 << " " << y + 1 << "\n"; return 0; }

4|0H. Random Tree Parking


depi表示i的深度,sizei表示i的子树的大小。这样的话i及其子树大小中,可以选择的点的个数就是[sizei,sizei+depi]

我们可以用树形dp,计算出fi,j表示i为根的子树中选择了j+sizei的方案数。其中jdepi

注意到树是完全随机的,因此可以保证深度是在logn附近,因此复杂度是O(nlogn)

#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 i32 inf = INT_MAX / 2; const int mod = 998244353; struct mint { i64 x; mint(int x = 0) : x(x) {} int val() const { return (x % mod + mod) % mod; } mint &operator=(int o) { return x = o, *this; } mint &operator+=(mint o) { return (x += o.x) >= mod && (x -= mod), *this; } mint &operator-=(mint o) { return (x -= o.x) < 0 && (x += mod), *this; } mint &operator*=(mint o) { return x = (i64) x * o.x % mod, *this; } mint &operator^=(int b) { mint w = *this, ret(1); for (; b; b >>= 1, w *= w) if (b & 1) ret *= w; return x = ret.x, *this; } mint &operator/=(mint o) { return *this *= (o ^= (mod - 2)); } friend mint operator+(mint a, mint b) { return a += b; } friend mint operator-(mint a, mint b) { return a -= b; } friend mint operator*(mint a, mint b) { return a *= b; } friend mint operator/(mint a, mint b) { return a /= b; } friend mint operator^(mint a, int b) { return a ^= b; } }; i32 main() { ios::sync_with_stdio(false), cin.tie(nullptr); int n; cin >> n; vector<mint> fact(n + 1), invFact(n + 1); fact[0] = 1; for (int i = 1; i <= n; i++) fact[i] = fact[i - 1] * i; invFact[n] = fact[n] ^ (mod - 2); for (int i = n; i; i--) invFact[i - 1] = invFact[i] * i; auto C = [fact, invFact, n](int x, int y) -> mint { return fact[x] * invFact[y] * invFact[x - y]; }; vector<vi> e(n + 1); vi dep(n + 1); dep[1] = 1; for (int i = 2, x; i <= n; i++) { cin >> x; dep[i] = dep[x] + 1; e[x].push_back(i); } auto conv = [C](vector<mint> &a, vector<mint> &b, int n, int x, int y) -> void { vector<mint> g(n + 1, 0); for (int i = 0; i <= n; i++) for (int j = 0; i + j <= n; j++) g[i + j] += a[i] * b[j] * C(x + y + i + j, x + i); a = move(g); }; vector<vector<mint>> f(n + 1); vi size(n + 1); for (int x = n; x; x--) { f[x] = vector<mint>(dep[x] + 1, 1); for (auto y: e[x]) { conv(f[x], f[y], dep[x], size[x], size[y]); size[x] += size[y]; } size[x]++; for (int i = 0; i < dep[x]; i++) f[x][i] = f[x][i + 1]; } cout << f[1][0].val() << "\n"; return 0; }

5|0I. Refresher into Midas


贪心的考虑,对于a一定是能用就用最好。多用b不会使得答案变差。

对于b有两种情况,一种情况是能用就用,还有一种时等待a回转,在使用a后立即使用b再使用a

第一种情况的周期是b,收益是ba+1

第二种情况的周期是baa,收益是ba+1

对于两种周期我们可以贪心的枚举一个,再计算出另一个周期的个数,对于最后剩下的时间就一直使用a就好了。

#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 i32 inf = INT_MAX / 2; void solve() { int a, b, m; cin >> a >> b >> m; int t1 = b, v1 = b / a + 1; int t2 = ((b + a - 1) / a) * a, v2 = (b + a - 1) / a + 1; int res = 2; for (int c1 = 0; c1 <= m / t1; c1++) { int c2 = (m - c1 * t1) / t2; int c3 = (m - c1 * t1 - c2 * t2) / a; res = max(res, c1 * v1 + c2 * v2 + c3 + 2); } cout << 160 * res << "\n"; return; } i32 main() { ios::sync_with_stdio(false), cin.tie(nullptr); int T; cin >> T; while (T--) solve(); return 0; }  

6|0J. Teleportation


题目的操作,其实可以转换成两种操作。第一种是移动ai步。第二种是移动1步。但是第二种操作前,必须有第一种操作。

这样的话直接做 bfs 即可

#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 i32 inf = INT_MAX / 2; i32 main() { ios::sync_with_stdio(false), cin.tie(nullptr); int n, k; cin >> n >> k; vi a(n); for (auto &i: a) cin >> i; vector dis(n, vi(2, inf)); vector vis(n, vi(2)); queue<pii> q; dis[0][0] = 0; q.emplace(0, 0); while (not q.empty()) { auto [x, t] = q.front(); q.pop(); if (vis[x][t]) continue; vis[x][t] = 1; int y = (x + a[x]) % n; if (dis[y][1] > dis[x][t] + 1) { dis[y][1] = dis[x][t] + 1; q.emplace(y, 1); } y = (x + 1) % n; if (t == 1 and dis[y][1] > dis[x][1] + 1) { dis[y][1] = dis[x][1] + 1; q.emplace(y, 1); } } cout << ranges::min(dis[k]); return 0; }  

__EOF__

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