The 2018 ACM-ICPC Asia Qingdao Regional Contest, Online (The 2nd Universal Cup. Stage 1: Qingdao)
A. Live Love
题意:\(n\)个数有\(m\)个数是好的,随意排列,求最长连续好子数组的长度和最短长度。
最长显然是\(m\)。最短可以把\(n-m\)个坏的拿出来,把数组分成了\(n-m+1\)个部分,然后这\(m\)个数放在均匀的插在每个部分,答案就是\(m\)除\(n-m+1\)向上取整。
点击查看代码
#include <bits/stdc++.h>
using i64 = long long;
void solve() {
int n, m;
std::cin >> n >> m;
std::cout << m << " " << (m + n - m) / (n - m + 1) << "\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;
}
B. Red Black Tree
题意:一棵以\(1\)为根的数。给出\(m\)个点是红色的,其它是黑色的。\(cost_i\)计算方式为,如果是红色则\(cost_i = 0\),否则\(cost_i\)为到最近红色祖先节点的距离。\(q\)次询问,每次给出\(k\)个点,你可以把树上一个点变成红色,使得这些的点的\(cost\)最大值最小。
首先计算\(cost\)。
对于每个询问,可以把这些点按\(cost\)排序,那么我们从大到小看,如果想让前\(i\)个点的\(cost\)都变小则需要选它们的最近公共祖先染为红色,染色后的价值如何快速计算?记\(dist_i\)为\(i\)到根的距离,那么取\(max(d_i) - d_{lca}\)。如果其中有些点与\(lca\)中间没有红色点,那么取值没问题,如果有的点和\(lca\)中间隔了一个红色点,那么肯定有选更少的几个更优,这我们已经计算过了。不影响答案。
点击查看代码
#include <bits/stdc++.h>
using i64 = long long;
void solve() {
int n, m, q;
std::cin >> n >> m >> q;
std::vector<int> st(n + 1);
for (int i = 0; i < m; ++ i) {
int x;
std::cin >> x;
st[x] = 1;
}
std::vector<std::vector<std::pair<int, int>>> adj(n + 1);
for (int i = 1; i < n; ++ i) {
int u, v, w;
std::cin >> u >> v >> w;
adj[u].emplace_back(v, w);
adj[v].emplace_back(u, w);
}
int lg = std::__lg(n) + 1;
std::vector f(n + 1, std::vector<int>(lg + 1));
std::vector<i64> d(n + 1), dist(n + 1), cost(n + 1);
auto dfs = [&](auto & self, int u, int fa) -> void {
if (st[u]) {
cost[u] = 0;
}
for (auto & [v, w] : adj[u]) {
if (v == fa) {
continue;
}
dist[v] = dist[u] + w;
cost[v] = cost[u] + w;
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);
}
};
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];
};
while (q -- ) {
int k;
std::cin >> k;
std::vector<std::pair<i64, int>> a(k);
for (int i = 0; i < k; ++ i) {
int x;
std::cin >> x;
a.emplace_back(cost[x], x);
}
std::ranges::sort(a, std::greater<>());
i64 ans = a[0].first;
i64 max = 0;
for (int i = 0, u = a[0].second; i < k; ++ i) {
max = std::max(max, dist[a[i].second]);
u = lca(u, a[i].second);
ans = std::min(ans, std::max(max - dist[u], i + 1 == k ? 0ll : a[i + 1].first));
}
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;
}
C. Halting Problem
题意:有五个指令,初始有一个\(x=0\),\(add\)指令使得\(x = (x + v) \% 256\),其它指令则类似\(goto\)语句,如果满足条件就会跳到其它语句。求有没有死循环。
\(x\)取值只有\(256\)中,可能开一个\(n\times 256\)的数组,\(bfs\)搜索。
点击查看代码
#include <bits/stdc++.h>
using i64 = long long;
void solve() {
int n;
std::cin >> n;
std::vector<std::array<int, 3>> op(n);
for (int i = 0; i < n; ++ i) {
std::string s;
std::cin >> s;
if (s == "add") {
int v;
std::cin >> v;
op[i] = {1, v, 0};
} else if (s == "beq") {
int v, k;
std::cin >> v >> k;
-- k;
op[i] = {2, v, k};
} else if (s == "bne") {
int v, k;
std::cin >> v >> k;
-- k;
op[i] = {3, v, k};
} else if (s == "blt") {
int v, k;
std::cin >> v >> k;
-- k;
op[i] = {4, v, k};
} else {
int v, k;
std::cin >> v >> k;
-- k;
op[i] = {5, v, k};
}
}
std::vector f(n + 1, std::array<int, 256>{});
f[0][0] = 1;
std::queue<std::pair<int, int>> q;
q.emplace(0, 0);
while (q.size()) {
auto [i, x] = q.front(); q.pop();
if (i == n) {
std::cout << "Yes\n";
return;
}
if (op[i][0] == 1) {
int nx = (x + op[i][1]) % 256;
if (i + 1 <= n && f[i + 1][nx] == 0) {
f[i + 1][nx] = 1;
q.emplace(i + 1, nx);
}
} else if (op[i][0] == 2) {
if (x == op[i][1] && f[op[i][2]][x] == 0) {
f[op[i][2]][x] = 1;
q.emplace(op[i][2], x);
} else if (i + 1 <= n && x != op[i][1] && f[i + 1][x] == 0) {
f[i + 1][x] = 1;
q.emplace(i + 1, x);
}
} else if (op[i][0] == 3) {
if (x != op[i][1] && f[op[i][2]][x] == 0) {
f[op[i][2]][x] = 1;
q.emplace(op[i][2], x);
} else if (i + 1 <= n && x == op[i][1] && f[i + 1][x] == 0) {
f[i + 1][x] = 1;
q.emplace(i + 1, x);
}
} else if (op[i][0] == 4) {
if (x < op[i][1] && f[op[i][2]][x] == 0) {
f[op[i][2]][x] = 1;
q.emplace(op[i][2], x);
} else if (i + 1 <= n && x >= op[i][1] && f[i + 1][x] == 0) {
f[i + 1][x] = 1;
q.emplace(i + 1, x);
}
} else {
if (x > op[i][1] && f[op[i][2]][x] == 0) {
f[op[i][2]][x] = 1;
q.emplace(op[i][2], x);
} else if (i + 1 <= n && x <= op[i][1] && f[i + 1][x] == 0) {
f[i + 1][x] = 1;
q.emplace(i + 1, x);
}
}
}
std::cout << "No\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;
}
D. Pixel Art
题意:一个\(n\times m\)的矩阵有\(k\)个不相交横条或竖条的格子画成了黑色。求前\(i\)行黑色格子数和黑色格子构成的联通块数。
每个格子按\(r1\)加入,\(r2 + 1\)删除,我们可以直接模拟。存储每个\([c1, c2]\)的区间,插入时看和上面左右有没有相邻的,用并查集维护联通块。
点击查看代码
#include <bits/stdc++.h>
using i64 = long long;
struct DSU {
std::vector<int> fa, cnt;
DSU();
DSU(int n) {
init(n);
}
void init(int n) {
fa.assign(n + 1, 0);
cnt.assign(n + 1, 1);
std::ranges::iota(fa, 0);
}
int find(int x) {
return x == fa[x] ? x : fa[x] = find(fa[x]);
}
bool merge(int x, int y) {
int u = find(x), v = find(y);
if (u == v) {
return false;
}
fa[v] = u;
cnt[u] += cnt[v];
return true;
}
int size(int u) {
return cnt[find(u)];
}
};
void solve() {
int n, m, k;
std::cin >> n >> m >> k;
std::vector<std::vector<int>> add(n + 1), del(n + 2);
std::vector<int> r1(k), c1(k), r2(k), c2(k);
for (int i = 0; i < k; ++ i) {
std::cin >> r1[i] >> c1[i] >> r2[i] >> c2[i];
add[r1[i]].push_back(i);
del[r2[i] + 1].push_back(i);
}
DSU dsu(k);
i64 cnt = 0;
i64 ans1 = 0, ans2 = 0;
std::map<std::pair<int, int>, int> mp;
for (int i = 1; i <= n; ++ i) {
for (auto & x : add[i]) {
auto it = mp.lower_bound(std::make_pair(c1[x], 0));
if (it != mp.begin()) {
-- it;
}
while (it != mp.end() && it->first.first <= c2[x]) {
if (it->first.second >= c1[x]) {
ans2 -= dsu.merge(x, mp[it->first]);
}
++ it;
}
}
for (auto & x : del[i]) {
cnt -= c2[x] - c1[x] + 1;
mp.erase({c1[x], c2[x]});
}
for (auto & x : add[i]) {
cnt += c2[x] - c1[x] + 1;
mp[{c1[x], c2[x]}] = dsu.find(x);
auto it = mp.find({c1[x], c2[x]});
if (it != mp.begin() && std::prev(it)->first.second == c1[x] - 1) {
ans2 -= dsu.merge(mp[it->first], mp[std::prev(it)->first]);
}
if (std::next(it) != mp.end() && std::next(it)->first.first == c2[x] + 1) {
ans2 -= dsu.merge(mp[it->first], mp[std::next(it)->first]);
}
}
ans1 += cnt;
ans2 += add[i].size();
std::cout << ans1 << " " << ans2 << "\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. Couleur
题意:一个数组,每次删掉一个位置,求剩下的数构成的连续子数组的最大的逆序对数。
首先用主席树维护区间\([l, r]\)的元素个数。
对于删除一个位置的操作,可以把它看作把原区间\([l, r]\)分裂为\([l, x - 1], [x + 1, r]\)。考虑怎么计算这两个区间的逆序对。
我们可以暴力计算小区间的逆序对,这样每次一个区间最多循环一半,而区间越来越小,时间复杂度是\(O(nlogn)\)的。怎么计算另一个区间的逆序对呢,发现可以用原区间的逆序对减去小区间的逆序对减去两个数分边在两边的逆序对减去和\(x\)有关的逆序对。
点击查看代码
#include <bits/stdc++.h>
using i64 = long long;
const int N = 1e5 + 5;
#define ls(u) tr[u].lson
#define rs(u) tr[u].rson
struct Node {
int lson, rson;
int sum;
}tr[N << 5];
int n, a[N], root[N], idx;
void insert(int & u, int v, int p, int l = 0, int r = n + 1) {
u = ++ idx;
tr[u] = tr[v];
tr[u].sum += 1;
if (l == r) {
return;
}
int mid = l + r >> 1;
if (p <= mid) {
insert(ls(u), ls(v), p, l, mid);
} else {
insert(rs(u), rs(v), p, mid + 1, r);
}
}
int query(int u, int v, int x, int y, int l = 0, int r = n + 1) {
if (x <= l && r <= y) {
return tr[v].sum - tr[u].sum;
}
int mid = l + r >> 1;
if (y <= mid) {
return query(ls(u), ls(v), x, y, l, mid);
} else if (x > mid) {
return query(rs(u), rs(v), x, y, mid + 1, r);
}
return query(ls(u), ls(v), x, mid, l, mid) + query(rs(u), rs(v), mid + 1, y, mid + 1, r);
}
i64 get(int l, int r) {
i64 res = 0;
for (int i = l; i <= r; ++ i) {
res += query(root[l - 1], root[i], a[i] + 1, n + 1);
}
return res;
}
void solve() {
std::cin >> n;
for (int i = 1; i <= n; ++ i) {
std::cin >> a[i];
}
idx = 0;
for (int i = 1; i <= n; ++ i) {
root[i] = 0;
}
for (int i = 1; i <= n; ++ i) {
insert(root[i], root[i - 1], a[i]);
}
std::multiset<i64> ans;
ans.insert(get(1, n));
std::map<int, std::pair<int, i64>> mp;
mp[1] = {n, *ans.begin()};
mp[n + 1] = {0, 0};
auto work = [&](int l, int r, int x) -> void {
i64 old = mp[l].second; mp.erase(mp.find(l));
ans.extract(old);
if (x - l < r - x) {
i64 A = 0, B = 0;
for (int i = l; i < x; ++ i) {
A += query(root[l - 1], root[i], a[i] + 1, n + 1);
B += query(root[x], root[r], 0, a[i] - 1);
}
B += query(root[l - 1], root[x - 1], a[x] + 1, n + 1);
B += query(root[x], root[r], 0, a[x] - 1);
if (l < x) {
mp[l] = {x - 1, A};
ans.insert(A);
}
if (x < r) {
mp[x + 1] = {r, old - A - B};
ans.insert(old - A - B);
}
} else {
i64 A = 0, B = 0;
for (int i = x + 1; i <= r; ++ i) {
B += query(root[x], root[i], a[i] + 1, n + 1);
A += query(root[l - 1], root[x - 1], a[i] + 1, n + 1);;
}
A += query(root[l - 1], root[x - 1], a[x] + 1, n + 1);
A += query(root[x], root[r], 0, a[x] - 1);
if (l < x) {
mp[l] = {x - 1, old - A - B};
ans.insert(old - A - B);
}
if (x < r) {
mp[x + 1] = {r, B};
ans.insert(B);
}
}
};
for (int i = 1; i <= n; ++ i) {
std::cout << *ans.rbegin() << " \n"[i == n];
i64 x;
std::cin >> x;
x ^= *ans.rbegin();
auto it = std::prev(mp.upper_bound(x));
work(it->first, it->second.first, x);
}
}
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;
}
H. Traveling on the Axis
题意:一个\(01\)数组,\(s_i\)代表\(i-1\)和\(i\)之间的状态,如果是\(1\)就能通行,所有位置每秒都会改变状态。记\(f(p, q)\)为从\(p\)走到\(q\)的时间,求\(\sum_{i=0}^{n-1} \sum_{j=i+1}^{n} f(i, j)\)。
手玩一会发现,对于\((i, j)\),需要\(j - i\)的时间,如果\(s_i == 0\),则多一秒,每有一对相邻相等的位置就多一秒。那么\(f(i, j) = j - i + (s_i == 0) + \sum_{k=i}^{j-1} s_k == s_{k+1}\)。
\(j-i\)显然直接刷。\(s_i\)为的地方可以枚举,如果固定\(i\)为左端点,则有\(n-i\)的区间需要加一。对于\(s_k = s_{k+1}\),看有多少区间包含这两个位置,就是\(k\times (n-k-1)\)个。
点击查看代码
#include <bits/stdc++.h>
using i64 = long long;
void solve() {
std::string s;
std::cin >> s;
int n = s.size();
i64 ans = 0;
for (int i = 1; i <= n; ++ i) {
ans += (i64)i * (n - i + 1);
}
for (int i = 0; i + 1 < n; ++ i) {
if (s[i] == s[i + 1]) {
ans += (i64)(i + 1) * (n - i - 1);
}
}
for (int i = 0; i < n; ++ i) {
if (s[i] == '0') {
ans += n - i;
}
}
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;
}
J. Press the Button
题意:有一个计数器、按钮、当前时间。给你\(a, b, c, d, v, t\),当前时间从\(0\)到\(t\),如果当前时间是\(a\)的倍数,则按按钮\(b\)下,如果是\(c\)的倍数则按\(d\)下,每次按按钮灯会亮且等到\(v+0.5\)秒后变暗。如果灯是亮的状态按钮被按则计数器加一。求\(t\)秒后计数器的值。
假设每次按按钮灯都是暗的,则有\((\lfloor \frac{t}{a} \rfloor + 1) \times b - \lfloor \frac{t}{a} \rfloor + (\lfloor \frac{t}{c} \rfloor + 1) \times d - \lfloor \frac{t}{c} \rfloor\)。现在考虑把多减的加回来,就是当一个数按了后,把灯变亮,另一个数在灯暗之前按的情况,每有一个这种情况答案加一。
那么我们需要两个按按钮的时间相差几,想到\(ax+by = d\)。这个时间一定是\(gcd(a, c)\)的倍数。那么可以枚举倍数求最小的\(x\),然后看范围内还有多少解。
点击查看代码
#include <bits/stdc++.h>
using i64 = long long;
auto exgcd(i64 a, i64 b, i64 & x, i64 & y) {
if (b == 0) {
x = 1, y = 0;
return a;
}
i64 d = exgcd(b, a % b, y, x);
y -= a / b * x;
return d;
}
void solve() {
i64 a, b, c, d, v, t;
std::cin >> a >> b >> c >> d >> v >> t;
if (a > c) {
std::swap(a, c);
std::swap(b, d);
}
if (v >= a) {
std::cout << (t / a + 1) * b + (t / c + 1) * d - 1 << "\n";
return;
}
i64 x, y;
i64 g = exgcd(a, c, x, y);
x = (x % (c / g) + c / g) % (c / g);
i64 ans = (t / a + 1) * b - t / a + (t / c + 1) * d - t / c - 2;
for (i64 i = 0; i * g <= v; ++ i) {
i64 xx = (x * i % (c / g) + c / g) % (c / g);
if (xx * a > t) {
continue;
}
ans += (t - xx * a) / (c / g * a) + 1;
}
std::swap(a, c);
std::swap(b, d);
exgcd(a, c, x, y);
for (i64 i = 1; i * g <= v; ++ i) {
i64 xx = (x * i % (c / g) + c / g) % (c / g);
if (xx * a > t) {
continue;
}
ans += (t - xx * a) / (c / g * a) + 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;
}
K. XOR Clique
题意:选一个子集使得没有\(a_i \oplus a_j \leq \min(a_i, a_j)\)。求最多选几个。
显然有两个数的最高位的\(1\)的位置相等。开个数组记录一些。
点击查看代码
#include <bits/stdc++.h>
using i64 = long long;
auto exgcd(i64 a, i64 b, i64 & x, i64 & y) {
if (b == 0) {
x = 1, y = 0;
return a;
}
i64 d = exgcd(b, a % b, y, x);
y -= a / b * x;
return d;
}
void solve() {
i64 a, b, c, d, v, t;
std::cin >> a >> b >> c >> d >> v >> t;
if (a > c) {
std::swap(a, c);
std::swap(b, d);
}
if (v >= a) {
std::cout << (t / a + 1) * b + (t / c + 1) * d - 1 << "\n";
return;
}
i64 x, y;
i64 g = exgcd(a, c, x, y);
x = (x % (c / g) + c / g) % (c / g);
i64 ans = (t / a + 1) * b - t / a + (t / c + 1) * d - t / c - 2;
for (i64 i = 0; i * g <= v; ++ i) {
i64 xx = (x * i % (c / g) + c / g) % (c / g);
if (xx * a > t) {
continue;
}
ans += (t - xx * a) / (c / g * a) + 1;
}
std::swap(a, c);
std::swap(b, d);
exgcd(a, c, x, y);
for (i64 i = 1; i * g <= v; ++ i) {
i64 xx = (x * i % (c / g) + c / g) % (c / g);
if (xx * a > t) {
continue;
}
ans += (t - xx * a) / (c / g * a) + 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;
}