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;
}
posted @ 2025-03-17 14:36  KevinLikesCoding  阅读(23)  评论(0)    收藏  举报