APIO 2024
A-September
模拟题。
取出 \(m\) 个人里包含相同元素的段,再判断删掉的是不是都是叶子就好,时间复杂度 \(O(nm)\)
#include <bits/stdc++.h>
using namespace std;
int solve(int n, int m, vector<int> fa, vector<vector<int>> s) {
int ans = 0, cnt = 0;
vector<int> d(n), c(n), del(n);
auto upd = [&](int j) -> void {
for (int i = 0; i < m; i++) {
if (!c[s[i][j]]) cnt++;
if (++c[s[i][j]] == m) {
del[s[i][j]] = 1;
for (int u = s[i][j]; !d[u] && del[u]; d[u = fa[u]]--) cnt--;
}
}
};
for (int i = 1; i < n; i++) d[fa[i]]++;
for (int l = 0, r; l < n - 1; ans++, l = r + 1) for (upd(r = l); cnt; upd(++r));
return ans;
}
B-Train
DP。
考虑把边看做点,把点看做边,按边的 \(A_i\) 从小到大遍历:
记 \(f_i\) 为到第 \(i\) 条边的起点的最小花费。
其中 \(cnt_{l, r}\) 表示在满足 \([L_i, R_i] \sube [l, r]\) 的餐 \(i\) 的个数,可以用主席树快速计算。
时间复杂度 \(\mathcal O(m^2 \log w)\)。
还不够,继续优化:
考虑在每个终点建一个单调队列,这样转移就是 \(\mathcal O(m \log w)\) 的了,难点在插入每顿餐的时候维护好这些单调队列。
因为在同一个星球上吃一顿饭的费用是确定的,所以对于每一个单调队列里的每一个 \(f_j + T_{X_i} \times cnt_{B_j + 1, A_i - 1}\),我们可以算出来它什么时候会把它前面的元素 \(f_k + T_{X_i} \times cnt_{B_k + 1, A_i - 1}\) 爆掉,即在该星球上吃的饭中 \(l_i \in [B_j + 1, B_k]\) 的个数等于 \(\left\lceil \dfrac{f(k) - f(j)}{T_{X_i}} \right\rceil\) 时。
然后我们标记这顿饭,并在将它考虑进来的时候把 \(k\) 删掉,因为随着 \(A_i\) 变大,\(cnt_{B_k + 1, A_i - 1} - cnt_{B_j + 1, A_i - 1}\) 不降,故此时 \(j\) 比 \(k\) 更优,未来一定 \(j\) 比 \(k\) 更优。
标记需要支持查询第 \(k\) 大,在前面的主席树上就能解决。
时间复杂度 \(\mathcal O((m + w)\log w)\)。
#include <bits/stdc++.h>
#include "train.h"
using namespace std;
typedef long long ll;
typedef pair<int, int> pii;
typedef vector<int> vint;
constexpr int N = 1e5 + 10, TN = N * 40, INF = 1e9;
int n, m, w, tot, rt[TN], id[N], hd[N], tl[N];
ll f[N];
bool vis[N];
struct Train {
int x, y, a, b, c;
bool operator<(const Train &rhs) const {return a < rhs.a;}
} tr[N];
struct Meal {int l, r, rk;} ml[N], mr[N];
bool cmpl(const Meal &lhs, const Meal &rhs) {return lhs.l != rhs.l ? lhs.l < rhs.l : lhs.r < rhs.r;}
bool cmpr(const Meal &lhs, const Meal &rhs) {return lhs.r != rhs.r ? lhs.r < rhs.r : lhs.l < rhs.l;}
vint c[N], to[N], pre[N];
priority_queue<pii, vector<pii>, greater<pii>> q;
namespace PST {
int tot;
struct Seg {int w, ls, rs;} t[TN];
inline int newt(int rt) {t[++tot] = t[rt]; return tot;}
void upd(int rt0, int &rt, int l, int r, int x) {
rt = newt(rt0), t[rt].w++;
if (l == r) return;
int mid = (l + r) >> 1;
x <= mid ? upd(t[rt0].ls, t[rt].ls, l, mid, x) : upd(t[rt0].rs, t[rt].rs, mid + 1, r, x);
}
int query(int pos, int l, int r, int x, int y) {
if (x <= l && r <= y) return t[pos].w;
int mid = (l + r) >> 1, res = 0;
if (x <= mid) res = query(t[pos].ls, l, mid, x, y);
if (y > mid) res += query(t[pos].rs, mid + 1, r, x, y);
return res;
}
int kth(int rx, int ry, int l, int r, int k) {
if (l == r) return l;
int mid = (l + r) >> 1, lsz = t[t[ry].ls].w - t[t[rx].ls].w;
if (k <= lsz) return kth(t[rx].ls, t[ry].ls, l, mid, k);
return kth(t[rx].rs, t[ry].rs, mid + 1, r, k - lsz);
}
}
ll solve(int n, int m, int w, vint T, vint X, vint Y, vint A, vint B, vint C, vint L, vint R) {
for (int i = 0; i < m; i++) tr[i] = {X[i], Y[i], A[i], B[i], C[i]};
for (int i = 0; i < w; i++) mr[i] = {L[i], R[i]};
sort(mr, mr + w, cmpr);
for (int i = 0; i < w; i++) mr[i].rk = i, ml[i] = mr[i];
sort(ml, ml + w, cmpl);
for (int i = 0; i < w; i++) {
if (i) rt[i] = rt[i - 1];
PST::upd(rt[i], rt[i], 0, w - 1, ml[i].rk);
}
sort(tr, tr + m);
memset(f, -1, sizeof(f)), memset(tl, -1, sizeof(tl));
to[0].emplace_back(m), pre[0].emplace_back(-1), f[m] = 0, tr[m].y = 0, tr[m].b = -1, hd[0] = tl[0] = 0;
int cnt = 0;
auto val = [&](int i) -> ll {
if (cnt) {
int p = upper_bound(ml, ml + w, Meal{tr[i].b, INF, INF}, cmpl) - ml;
return f[i] + 1ll * (cnt - (p ? PST::query(rt[p - 1], 0, w - 1, 0, cnt - 1) : 0)) * T[tr[i].y];
}
return f[i];
};
auto kth = [&](int l, int r, int k) -> int {
int x = lower_bound(ml, ml + w, Meal{l, -1, -1}, cmpl) - ml, y = upper_bound(ml, ml + w, Meal{r, INF, INF}, cmpl) - ml;
if (!y || y - x < k) return -1;
return PST::kth(x ? rt[x - 1] : 0, rt[y - 1], 0, w - 1, k);
};
auto calc = [&](int i) -> void {
int p = tr[i].y, j = to[p][pre[p][id[i]]], rk = kth(tr[j].b + 1, tr[i].b, (f[i] - f[j] + T[p] - 1) / T[p]);
if (rk != -1) c[rk].emplace_back(i);
};
for (int i = 0; i < m; i++) {
while (cnt < w && mr[cnt].r < tr[i].a) {
int cur = cnt++;
for (int x : c[cur]) if (!vis[x]) {
int p = tr[x].y;
if (id[x] == hd[p]) continue;
while (val(to[p][pre[p][id[x]]]) >= val(x)) {
vis[to[p][pre[p][id[x]]]] = 1;
if (pre[p][id[x]] == hd[p]) {hd[p] = id[x]; break;}
pre[p][id[x]] = pre[p][pre[p][id[x]]];
}
if (hd[p] != id[x]) calc(x);
}
}
while (!q.empty() && q.top().first <= tr[i].a) {
int x = q.top().second; q.pop();
int p = tr[x].y;
while (hd[p] <= tl[p] && val(x) <= val(to[p][tl[p]])) {
if (hd[p] == tl[p]) {hd[p] = to[p].size(); break;}
vis[to[p][tl[p]]] = 1, tl[p] = pre[p][tl[p]];
}
id[x] = to[p].size(); to[p].emplace_back(x);
if (hd[p] > tl[p]) pre[p].emplace_back(-1), hd[p] = tl[p] = id[x];
else pre[p].emplace_back(tl[p]), tl[p] = id[x], calc(x);
}
int p = tr[i].x;
if (hd[p] <= tl[p]) f[i] = val(to[p][hd[p]]) + tr[i].c, q.emplace(tr[i].b, i);
}
cnt = w; ll ans = 9e18;
for (int i = 0; i < m; i++) if (tr[i].y == n - 1 && f[i] != -1) ans = min(ans, val(i));
return ans < 9e18 ? ans : -1;
}
C-show
乱搞。
树的规模肯定越大越好,所以取 \(n = 5000\)。
对每一个点 \(i \ge 2\),连边 \(i \to X \bmod (i - 1) + 1\)。得到 \(2499\) 个同余方程,可以解出 \(X\)。
也不用 exCRT,朴素 \(\mathcal O(n^2)\) 解就行。
Alice:
#include <bits/stdc++.h>
#include "Alice.h"
using namespace std;
typedef pair<int, int> pii;
typedef long long ll;
constexpr int N = 5e3;
vector<pii> Alice() {
vector<pii> res;
ll x = setN(N);
for (int i = 2; i <= N; i++) res.emplace_back(pii(i, x % (i - 1) + 1));
return res;
}
Bob:
#include <bits/stdc++.h>
#include "Bob.h"
using namespace std;
typedef pair<int, int> pii;
typedef long long ll;
typedef __int128 lll;
ll Bob(vector<pii> v) {
lll x = 0, p = 1;
for (auto e : v) {
int xi = e.first - 1, pi = e.second - 1;
while (x % pi != xi) x += p;
p = p * pi / __gcd(p, lll(pi));
if (p > 1e18) return x;
}
return x;
}