The 2023 ICPC Asia Macau Regional Contest
A. (-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;
}
D. Graph of Maximum Degree 3
对于一个点为\(x\)的图,最多只有\(\frac{3x}{2}\)条边,如果只用红色边联通最少需要\(x-1\)条边,如果只用蓝色边联通最少也需要\(x-1\)条边。因此符合条件的图一定满足
因此符合条件的图点数不超过\(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;
}
E. 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;
}
H. Random Tree Parking
记\(dep_i\)表示\(i\)的深度,\(size_i\)表示\(i\)的子树的大小。这样的话\(i\)及其子树大小中,可以选择的点的个数就是\([size_i,size_i+dep_i]\)。
我们可以用树形dp,计算出\(f_{i,j}\)表示\(i\)为根的子树中选择了\(j+size_i\)的方案数。其中\(j\le dep_i\)。
注意到树是完全随机的,因此可以保证深度是在\(\log n\)附近,因此复杂度是\(O(n\log n)\)。
#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;
}
I. Refresher into Midas
贪心的考虑,对于\(a\)一定是能用就用最好。多用\(b\)不会使得答案变差。
对于\(b\)有两种情况,一种情况是能用就用,还有一种时等待\(a\)回转,在使用\(a\)后立即使用\(b\)再使用\(a\)。
第一种情况的周期是\(b\),收益是\(\left \lfloor \frac{b}{a} \right \rfloor + 1\)。
第二种情况的周期是\(\left \lceil \frac{b}{a} \right \rceil a\),收益是\(\left \lceil \frac{b}{a} \right \rceil +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;
}
J. Teleportation
题目的操作,其实可以转换成两种操作。第一种是移动\(a_i\)步。第二种是移动\(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;
}