The 2024 ICPC Asia East Continent Online Contest (I)
A. World Cup
这道题目难点主要是读懂题意,然后按照题意手玩一下就出来了。
按照题目手玩一下样例就可以找到规律,在采取最优分组策略的情况下,能够影响的结果的就是比自己分数的低的人的个数。
当进入 32 强后,如果有2个人比你的分数低,你就晋级。
当进入 16 强后,有两种情况,在你自己的组排第一,要有3 个人比你低,你要赢得另一组的第二,还有3个人比你低。另一种情况是在你的组排第二,你要比 2 个人要高,同时你要比另一组的第一高,因此还有4 个人比你低。所以晋级的条件是有 6 个人比你分数低。
进入 8 强后,你本身比 6 个人分数高,你要赢另一个人,同时他也比 6 个人高,因此晋级条件是比 13 个人分数高。
进入 4 强后,推理同 8 强你要比 27 个人高。
进入决赛后,很简单比所有人都高,也就是 31 个人。
#include <bits/stdc++.h>
using namespace std;
using i32 = int32_t;
using i64 = long long;
#define int i64
using vi = vector<int>;
void solve() {
int n = 32;
vi a(n);
for (auto &i: a) cin >> i;
int cnt = 0;
for (int i = 1; i < n; i++)
cnt += a[0] > a[i];
if (cnt < 2) cout << "32\n";
else if (cnt < 6) cout << "16\n";
else if (cnt < 13) cout << "8\n";
else if (cnt < 27) cout << "4\n";
else if (cnt < 31) cout << "2\n";
else cout << "1\n";
}
i32 main() {
ios::sync_with_stdio(false), cin.tie(nullptr);
int T;
cin >> T;
while (T--)
solve();
return 0;
}
F. Make Max
可以想到的是,一定是吧最小值一点点变大,这样是最优的。
那么我们就应该假设我们找到了最小值,并且最小值是连续的一段\([l,r]\)我们就应该找到这一段的左侧和右侧,选取较小值吧这一段变大,产生的贡献就是\(r-1+1\)。
这里我用了类似珂朵莉树的方法,分别按照顺序维护段和大小维护段,然后暴力的模拟就好了,以为每次操作可以使得段的数量至少减1,复杂度\(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>;
struct seg1 {
int l, r, v;
seg1(int l, int r = 0, int v = 0) : l(l), r(r), v(v) {}
bool operator<(const seg1 &b) const {
return l < b.l;
}
};
struct seg2 {
int l, r, v;
seg2(int l, int r = 0, int v = 0) : l(l), r(r), v(v) {}
bool operator<(const seg2 &b) const {
if (v != b.v) return v < b.v;
return l < b.l;
}
};
void solve() {
int n;
cin >> n;
set<seg1> cnt1;
set<seg2> cnt2;
vi a(n + 1);
for (int i = 1; i <= n; i++) cin >> a[i];
for (int l = 1, r; l <= n;) {
r = l;
while (r + 1 <= n and a[r + 1] == a[l]) r++;
cnt1.emplace(l, r, a[l]);
cnt2.emplace(l, r, a[l]);
l = r + 1;
}
int res = 0;
while (cnt2.size() > 1) {
auto [l, r, v] = *cnt2.begin();
cnt2.erase(cnt2.begin());
cnt1.erase(cnt1.lower_bound(seg1(l, r, v)));
res += r - l + 1;
auto it = cnt1.lower_bound(seg1(l));
if (it == cnt1.end()) {
it = prev(it);
auto [nl, nr, nv] = *it;
cnt1.erase(it);
cnt2.erase(cnt2.lower_bound(seg2(nl, nr, nv)));
nl = min(nl, l), nr = max(nr, r), nv = max(nv, v);
cnt1.emplace(nl, nr, nv);
cnt2.emplace(nl, nr, nv);
} else if (it == cnt1.begin()) {
auto [nl, nr, nv] = *it;
cnt1.erase(it);
cnt2.erase(cnt2.lower_bound(seg2(nl, nr, nv)));
nl = min(nl, l), nr = max(nr, r), nv = max(nv, v);
cnt1.emplace(nl, nr, nv);
cnt2.emplace(nl, nr, nv);
} else {
auto pit = prev(it);
if (pit->v == it->v) {
auto [nl, nr, nv] = *it;
cnt1.erase(it);
cnt2.erase(cnt2.lower_bound(seg2(nl, nr, nv)));
auto [pl, pr, pv] = *pit;
cnt1.erase(pit);
cnt2.erase(cnt2.lower_bound(seg2(pl, pr, pv)));
nl = min(nl, pl), nr = max(nr, pr);
nl = min(nl, l), nr = max(nr, r), nv = max(nv, v);
cnt1.emplace(nl, nr, nv);
cnt2.emplace(nl, nr, nv);
} else {
if (pit->v < it->v) it = pit;
auto [nl, nr, nv] = *it;
cnt1.erase(it);
cnt2.erase(cnt2.lower_bound(seg2(nl, nr, nv)));
nl = min(nl, l), nr = max(nr, r), nv = max(nv, v);
cnt1.emplace(nl, nr, nv);
cnt2.emplace(nl, nr, nv);
}
}
}
cout << res << "\n";
}
i32 main() {
ios::sync_with_stdio(false), cin.tie(nullptr);
int T;
cin >> T;
while (T--)
solve();
return 0;
}
G. The Median of the Median of the Median
先用对顶堆求出\(b\)数组。
然后我们可以二分答案,二分枚举\(x\),然后把\(b\)数组内小于等于\(x\)的比标记为\(1\)。然后用前缀和维护一下,当我需要计算\(c\)数组的时候就可以\(O(1)\)的计算出区间内总数和小于等于\(x\)的数量,因此可以判断出当前的\(c\)是否大于等于\(x\)。然后我们在统计\(c\)种大于等于\(x\)的个数,如果超过了一半,说明中位数大于等于\(x\)。
#include <bits/stdc++.h>
using namespace std;
using i32 = int32_t;
using i64 = long long;
#define int i64
using vi = vector<int>;
struct Median {
int m, n;
priority_queue<int> l;
priority_queue<int, vi, greater<>> r;
Median() {
n = m = 0;
}
void insert(int x) {
n++;
if (n == 1) {
m = x;
return;
}
if (x <= m) l.push(x);
else r.push(x);
int t = (n + 1) / 2 - 1;
while (l.size() > t) {
r.push(m), m = l.top(), l.pop();
}
while (l.size() < t) {
l.push(m), m = r.top(), r.pop();
}
}
};
i32 main() {
ios::sync_with_stdio(false), cin.tie(nullptr);
int n;
cin >> n;
vi a(n + 1);
for (int i = 1; i <= n; i++) cin >> a[i];
vector b(n + 1, vi(n + 1));
for (int i = 1; i <= n; i++) {
Median median;
for (int j = i; j <= n; j++) {
median.insert(a[j]);
b[i][j] = median.m;
}
}
auto t = [](int k) {
k = (1 + k) * k / 2;
return (k + 1) / 2;
};
auto check = [&](int x) -> bool {
vector c(n + 2, vi(n + 2));
for (int j = 1; j <= n; j++)
for (int i = j; i >= 1; i--)
c[i][j] = (b[i][j] <= x) + c[i + 1][j];
int cnt = 0;
for (int l = 1; l <= n; l++)
for (int r = l, sum = 0; r <= n; r++) {
sum += c[l][r];
if (sum >= t(r - l + 1))
cnt++;
}
return cnt >= t(n);
};
int l = 1, r = 1e9, res = -1;
while (l <= r) {
int mid = (l + r) / 2;
if (check(mid)) res = mid, r = mid - 1;
else l = mid + 1;
}
cout << res;
return 0;
}
M. Find the Easiest Problem
用map<string,set<string>>
统计一下每道题目的通过人数就好了。
#include <bits/stdc++.h>
using namespace std;
using i32 = int32_t;
using i64 = long long;
void solve() {
int n;
cin >> n;
map<string, set<string>> cnt;
for (string team, problem, result; n; n--) {
cin >> team >> problem >> result;
if (result != "accepted") continue;
cnt[problem].insert(team);
}
string res;
int val = 0;
for (auto [i, v]: cnt) {
if (v.size() > val) val = v.size(), res = i;
}
cout << res << "\n";
}
i32 main() {
ios::sync_with_stdio(false), cin.tie(nullptr);
int T;
cin >> T;
while (T--)
solve();
return 0;
}