APIO 2018-2024 做题笔记
APIO 2018-2024 做题笔记
太难了。(持续更新中……
[APIO2018] 铁人两项
先缩掉点双,考虑两个点直接能经过多少个点。
不难发现可以将方点设成点双大小,原点设 \(-1\),这样两点路径的权值和就是可以经过点的个数。
树上随便 dp 一下即可。
时间复杂度 \(O(n)\)。
ケロシの代码
const int N = 2e5 + 5;
int n, m, s, tot;
vector<int> e[N];
int fi[N], ne[N << 1], to[N << 1], ecnt;
int dfn[N], low[N], cnt;
int stk[N], tp;
int w[N], sz[N];
ll ans;
void add(int u, int v) {
ne[++ ecnt] = fi[u];
to[ecnt] = v;
fi[u] = ecnt;
}
void tarjan(int u) {
dfn[u] = low[u] = ++ cnt;
stk[++ tp] = u;
s ++;
for(int v : e[u]) {
if(! dfn[v]) {
tarjan(v);
chmin(low[u], low[v]);
if(low[v] == dfn[u]) {
tot ++;
w[tot] = 2;
while(stk[tp] != v) {
add(tot, stk[tp]);
add(stk[tp], tot);
w[tot] ++;
tp --;
}
add(tot, stk[tp]);
add(stk[tp], tot);
tp --;
add(u, tot);
add(tot, u);
}
}
else {
chmin(low[u], dfn[v]);
}
}
}
void dfs(int u, int fa) {
sz[u] = (u <= n);
for(int i = fi[u]; i; i = ne[i]) {
int v = to[i];
if(v == fa) continue;
dfs(v, u);
ans += 2ll * sz[u] * sz[v] * w[u];
sz[u] += sz[v];
}
ans += 2ll * sz[u] * (s - sz[u]) * w[u];
}
void solve() {
cin >> n >> m;
REP(_, m) {
int u, v;
cin >> u >> v;
e[u].push_back(v);
e[v].push_back(u);
}
FOR(i, 1, n) w[i] = - 1;
tot = n;
FOR(u, 1, n) if(! dfn[u]) {
s = 0;
tarjan(u);
dfs(u, 0);
}
cout << ans << endl;
}
[APIO2018] 选圆圈
注意到,对于有交大圆 \(A\) 和 小圆 \(B\),设 \(A_l\) 为圆 \(A\) 最左处的横坐标,即 \(A_l=A_x-A_r\),\(A_r\) 同理,
那么一定有 \(B_l \in [A_l,A_r]\) 或 \(B_r \in [A_l,A_r]\)。
对着这个区间暴力扫一遍即可。
但是考虑可能会卡你,所以将坐标系旋转一个角度即可。
ケロシの代码
const int N = 1e6 + 5;
struct PointD {
double x, y;
PointD(double x = 0, double y = 0): x(x), y(y) {}
};
struct PointL {
ll x, y;
PointL(ll x = 0, ll y = 0): x(x), y(y) {}
};
int n;
PointL a[N];
PointD A[N];
int r[N], id[N], ans[N];
struct Node {
double x; int i;
bool operator < (const Node & A) const {
if(x == A.x) return i < A.i;
return x < A.x;
}
} p[N];
ll sq(ll x) {
return x * x;
}
bool check(int u, int v) {
return sq(r[u] + r[v]) - sq(a[u].x - a[v].x) - sq(a[u].y - a[v].y) >= 0;
}
PointD Rotate(PointL A, double r) {
return PointD(A.x * cos(r) - A.y * sin(r), A.y * cos(r) + A.x * sin(r));
}
void solve() {
cin >> n;
FOR(i, 1, n) cin >> a[i].x >> a[i].y >> r[i];
mt19937 rnd(time(0));
double rd = (rnd() % 1000) / 114.;
FOR(i, 1, n) A[i] = Rotate(a[i], rd);
FOR(i, 1, n) id[i] = i;
sort(id + 1, id + n + 1, [&] (int x, int y) {
if(r[x] == r[y]) return x < y;
return r[x] > r[y];
});
FOR(i, 1, n) p[i] = {A[i].x - r[i], i};
FOR(i, 1, n) p[i + n] = {A[i].x + r[i], i};
sort(p + 1, p + n * 2 + 1);
FOR(i, 1, n) if(! ans[id[i]]) {
int u = id[i];
int L = lower_bound(p + 1, p + n * 2 + 1, (Node){A[u].x - r[u], 0}) - p - 2;
chmax(L, 1);
FOR(j, L, n * 2) {
int v = p[j].i;
if(p[j].x > A[u].x + r[u]) break;
if(ans[v]) continue;
if(check(u, v)) ans[v] = u;
}
}
FOR(i, 1, n) cout << ans[i] << " "; cout << endl;
}
[APIO2023] 赛博乐园 / cyberland
考虑倒过来转移,设 \(f_{u,j}\) 为从点 \(h\) 到点 \(u\),已经用了 \(j\) 次除以二了,那么接下来通过边的代价就变为了 \(\frac{w}{2^j}\),直接用 dijkstra 转移即可。
注意到 \(\frac{w}{2^j}\) 在 \(j\) 很大的时候几乎不计,所以只计算小于等于 \(70\) 的 \(j\) 即可。
时间复杂度 \(O(NK \log NK)\)。
ケロシの代码
const int N = 1e5 + 5;
const int K = 70;
const ll LNF = 2e18;
double pw[K + 1], d[N][K + 1];
bool vis[N][K + 1];
vector<PII> e[N];
double solve(
int n, int m, int k, int ed,
vector<int> x, vector<int> y, vector<int> c, vector<int> a
) {
chmin(k, K);
REP(i, n) e[i].clear();
REP(i, m) {
e[x[i]].push_back({y[i], c[i]});
e[y[i]].push_back({x[i], c[i]});
}
pw[0] = 1;
FOR(i, 1, K) pw[i] = pw[i - 1] / 2;
priority_queue<pair<double, PII>, vector<pair<double, PII>>, greater<pair<double, PII>>> pq;
REP(i, n) FOR(j, 0, K) d[i][j] = LNF;
REP(i, n) FOR(j, 0, K) vis[i][j] = 0;
d[ed][0] = 0;
pq.push({0, {ed, 0}});
while(! pq.empty()) {
auto h = SE(pq.top());
pq.pop();
int u, j; tie(u, j) = h;
if(vis[u][j]) continue;
vis[u][j] = 1;
for(auto E : e[u]) {
int v, w; tie(v, w) = E;
if(v == ed) continue;
if(j == K) {
if(d[v][K] > d[u][K]) {
d[v][K] = d[u][K];
pq.push({d[v][K], {v, K}});
}
continue;
}
if(a[v] == 0) {
if(d[v][K] > d[u][j] + w * pw[j]) {
d[v][K] = d[u][j] + w * pw[j];
pq.push({d[v][K], {v, K}});
}
continue;
}
if(a[v] == 2 && j < k) {
if(d[v][j + 1] > d[u][j] + w * pw[j]) {
d[v][j + 1] = d[u][j] + w * pw[j];
pq.push({d[v][j + 1], {v, j + 1}});
}
}
if(d[v][j] > d[u][j] + w * pw[j]) {
d[v][j] = d[u][j] + w * pw[j];
pq.push({d[v][j], {v, j}});
}
}
}
double ans = LNF;
FOR(i, 0, K) chmin(ans, d[0][i]);
if(ans > LNF / 2) return - 1;
else return ans;
}
[APIO2024] 九月
考虑对于单个序列,若出现儿子在祖先后面,则可以缩掉。
再考虑多个序列,每个点肯定在一个块里面,可以继续缩。
时间复杂度 \(O(NM \log N)\)。
ケロシの代码
const int N = 1e5 + 5;
vector<int> e[N];
int sz[N], dfn[N], cnt;
int lst[N];
struct SgT {
int le[N << 2], ri[N << 2];
int F[N << 2];
void pushup(int u) {
F[u] = max(F[u << 1], F[u << 1 | 1]);
}
void build(int u, int l, int r) {
le[u] = l, ri[u] = r;
if(l == r) {
F[u] = 0;
return;
}
int mid = l + r >> 1;
build(u << 1, l, mid);
build(u << 1 | 1, mid + 1, r);
pushup(u);
}
void modify(int u, int p, int x) {
if(le[u] == ri[u]) {
F[u] = x;
return;
}
int mid = le[u] + ri[u] >> 1;
if(p <= mid) modify(u << 1, p, x);
else modify(u << 1 | 1, p, x);
pushup(u);
}
int query(int u, int l, int r) {
if(l <= le[u] && ri[u] <= r) {
return F[u];
}
int mid = le[u] + ri[u] >> 1;
int res = 0;
if(l <= mid) chmax(res, query(u << 1, l, r));
if(mid < r) chmax(res, query(u << 1 | 1, l, r));
return res;
}
} t;
void dfs(int u) {
dfn[u] = ++ cnt;
sz[u] = 1;
for(int v : e[u]) {
dfs(v);
sz[u] += sz[v];
}
}
int solve(int n, int m, vector<int> p, vector<vector<int>> S) {
cnt = 0;
REP(i, n) e[i].clear();
FOR(i, 1, n - 1) e[p[i]].push_back(i);
dfs(0);
vector<PII> f;
REP(i, n - 1) f.push_back({i, i});
for(auto A : S) {
t.build(1, 1, n);
ROF(i, n - 2, 0) {
int u = A[i];
int val = t.query(1, dfn[u], dfn[u] + sz[u] - 1);
if(val > i) f.push_back({i, val});
t.modify(1, dfn[u], i);
}
}
REP(i, m) {
if(i) REP(j, n - 1) {
int l = j, r = lst[S[i][j]];
if(l > r) swap(l, r);
f.push_back({l, r});
}
REP(j, n - 1) lst[S[i][j]] = j;
}
sort(ALL(f));
int ans = 0;
int r = - 1;
for(auto h : f) {
if(FI(h) <= r) chmax(r, SE(h));
else ans ++, r = SE(h);
}
return ans;
}