只要你跑的够快,锅就追不上你

0%

「APIO 2019」全套题解

目录

奇怪装置

「Luogu P5444」奇怪装置

题目描述

给定 $A, B$ 以及 $n$ 个时间区间,时钟在时刻 $t$ 会显示 $(t + \lfloor \frac{t}{B} \rfloor \bmod A, t \bmod B)$。问共会出现多少个不同的时刻。

数据范围:$n \le 10^6, 1 \le AB \le 10^{18}, 0 \le l_i \le r_i \le 10^{18}$。

思路分析

考虑两个时刻什么时候显示的二元组相等。设 $t_2 = t_1 + m B$,那么有:

设 $\lfloor \frac{t_1}{B} \rfloor = k$,则:

所以 $A \vert m(B + 1)$,那么 $m$ 的最小值就是 $\frac{A}{\gcd(A, B + 1)}$,也就是说循环节的长度为 $\frac{AB}{\gcd(A, B + 1)}$。这样我们对所有时间区间处理一下然后用扫描线求它们的交即可。时间复杂度 $O(n \log n)$。

代码实现

1
#include <bits/stdc++.h>
2
using namespace std;
3
4
typedef long long ll;
5
const int maxn = 4e6;
6
int n, m, tot, sum[maxn + 3];
7
ll A, B, C, l, r, temp[maxn + 3];
8
pair<ll, ll> sect[maxn + 3];
9
10
ll gcd(ll a, ll b) {
11
	return b ? gcd(b, a % b) : a;
12
}
13
14
int main() {
15
	scanf("%d %lld %lld", &n, &A, &B);
16
	C = A / gcd(A, B + 1) * B;
17
	for (int i = 1; i <= n; i++) {
18
		scanf("%lld %lld", &l, &r);
19
		if (r - l + 1 >= C) {
20
			printf("%lld\n", C);
21
			return 0;
22
		}
23
		l %= C, r %= C;
24
		if (l <= r) {
25
			sect[++tot] = make_pair(l, r);
26
		} else {
27
			sect[++tot] = make_pair(0, r);
28
			sect[++tot] = make_pair(l, C - 1);
29
		}
30
	}
31
	for (int i = 1; i <= tot; i++) {
32
		temp[++m] = sect[i].first;
33
		temp[++m] = sect[i].second + 1;
34
	}
35
	sort(temp + 1, temp + m + 1);
36
	for (int i = 1; i <= tot; i++) {
37
		int x = lower_bound(temp + 1, temp + m + 1, sect[i].first) - temp;
38
		int y = lower_bound(temp + 1, temp + m + 1, sect[i].second + 1) - temp;
39
		sum[x]++, sum[y]--;
40
	}
41
	ll ans = 0;
42
	for (int i = 1; i <= m; i++) {
43
		sum[i] += sum[i - 1];
44
		if (sum[i]) {
45
			ans += temp[i + 1] - temp[i];
46
		}
47
	}
48
	printf("%lld\n", ans);
49
	return 0;
50
}

桥梁

「Luogu P5443」桥梁

题目描述

给定一个带权无向图,支持修改边权,以及询问从一个点出发只经过边权不小于 $w$ 的边能到达多少个点。

数据范围:$n \le 5 \times 10^4, m, q \le 10^5$。

思路分析

如果没有修改可以离线做,有了修改后我们就考虑对操作分块,之前的修改离线加入,块内的修改暴力加入。具体地,我们将所有操作分成大小为 $b$ 的块。对于某一个块,我们离线地回答块内部的所有询问。

这时,我们可以把边分成两类:在本块内部有修改的和没有修改的。对于没有修改的边,我们将它们排序,然后离线的做;对于有修改的,它们的数量肯定不超过 $b$,我们就对于每个询问扫一遍这些边,看一下在这个询问的时间点上,它的权值是多少。这样如果 $b = \sqrt q$,那么时间复杂度是 $O(q \sqrt q \log m)$ 的。

需要注意算法的常数优化。有两个小技巧:

  • 并查集在执行 $\text{fa}[u] \leftarrow v, \text{size}[v] \leftarrow \text{size}[u] + \text{size}[v]$ 后如果想要撤销,我们只需要记录 $u$,因为剩下的都可以倒推出来。
  • 维护所有边按权值排序的数组时,加入每一块时不需要整体排序,只要做一次块内排序和一次归并即可。归并可以使用 std::merge() 函数,具体见代码。

另外,经试验,分块时块的大小 $b$ 为 $750$ 时常数较为优秀。

代码实现

1
#include <bits/stdc++.h>
2
using namespace std;
3
4
namespace __main__ {
5
	const int maxn = 5e4, maxm = 1e5, b = 750, maxb = b * 2;
6
	int n, m, q, w[maxm + 3], tot, vis[maxm + 3], last[maxm + 3];
7
	int cnt, lft[maxb + 3], rht[maxb + 3], num[maxb + 3], wei[maxb + 3];
8
	int all, temp[maxb + 3], cur[maxm + 3], ans[maxm + 3];
9
	int fa[maxn + 3], sz[maxn + 3], top, st[maxb + 3];
10
	pair<int, int> e[maxm + 3], pr[maxm + 3], t_1[maxm + 3], t_2[maxb + 3];
11
12
	struct event {
13
		int id, op, x, w;
14
		friend bool operator < (const event &a, const event &b) {
15
			return a.id < b.id;
16
		}
17
	} ev[maxm + 3];
18
19
	bool comp_w(const event &a, const event &b) {
20
		return a.w < b.w;
21
	}
22
23
	void get_mods(int l, int r) {
24
		++tot, cnt = 0, all = 0;
25
		for (int i = l; i <= r; i++) {
26
			if (ev[i].op == 1) {
27
				if (vis[ev[i].x] != tot) {
28
					vis[ev[i].x] = tot;
29
					last[ev[i].x] = l;
30
					cur[ev[i].x] = w[ev[i].x];
31
					temp[++all] = ev[i].x;
32
				}
33
				lft[++cnt] = last[ev[i].x], rht[cnt] = i - 1;
34
				num[cnt] = ev[i].x, wei[cnt] = cur[ev[i].x];
35
				last[ev[i].x] = i + 1, cur[ev[i].x] = ev[i].w;
36
			}
37
		}
38
		for (int i = 1; i <= all; i++) {
39
			lft[++cnt] = last[temp[i]], rht[cnt] = r;
40
			num[cnt] = temp[i], wei[cnt] = cur[temp[i]];
41
		}
42
	}
43
44
	int find(int x) {
45
		return x == fa[x] ? x : find(fa[x]);
46
	}
47
48
	void add(int u, int v, bool flag) {
49
		u = find(u), v = find(v);
50
		if (u == v) return;
51
		if (sz[u] < sz[v]) swap(u, v);
52
		if (flag) st[++top] = v;
53
		fa[v] = u, sz[u] += sz[v];
54
	}
55
56
	void add(int x, bool flag) {
57
		add(e[x].first, e[x].second, flag);
58
	}
59
60
	void cancel() {
61
		int u = st[top--];
62
		sz[fa[u]] -= sz[u], fa[u] = u;
63
	}
64
65
	void main() {
66
		scanf("%d %d", &n, &m);
67
		for (int i = 1, x, y, z; i <= m; i++) {
68
			scanf("%d %d %d", &x, &y, &z);
69
			x > y ? swap(x, y) : void();
70
			e[i] = make_pair(x, y), w[i] = -z;
71
		}
72
		for (int i = 1; i <= m; i++) {
73
			pr[i] = make_pair(w[i], i);
74
		}
75
		sort(pr + 1, pr + m + 1);
76
		scanf("%d", &q);
77
		for (int i = 1; i <= q; i++) {
78
			scanf("%d %d %d", &ev[i].op, &ev[i].x, &ev[i].w);
79
			ev[i].id = i, ev[i].w = -ev[i].w;
80
		}
81
		for (int l = 1, r = min(q, b); l <= q; l += b, r = min(q, r + b)) {
82
			get_mods(l, r);
83
			sort(ev + l, ev + r + 1, comp_w);
84
			for (int i = 1; i <= n; i++) {
85
				fa[i] = i, sz[i] = 1;
86
			}
87
			top = 0;
88
			int p = 1;
89
			for (int i = l; i <= r; i++) {
90
				if (ev[i].op == 2) {
91
					while (p <= m && pr[p].first <= ev[i].w) {
92
						if (vis[pr[p].second] != tot) {
93
							add(pr[p].second, false);
94
						}
95
						p++;
96
					}
97
				}
98
				for (int j = 1; j <= cnt; j++) {
99
					if (ev[i].id >= lft[j] && ev[i].id <= rht[j] && ev[i].w >= wei[j]) {
100
						add(num[j], true);
101
					}
102
				}
103
				ans[ev[i].id] = sz[find(ev[i].x)];
104
				while (top) {
105
					cancel();
106
				}
107
			}
108
			sort(ev + l, ev + r + 1);
109
			for (int i = l; i <= r; i++) {
110
				if (ev[i].op == 2) {
111
					printf("%d\n", ans[i]);
112
				}
113
			}
114
			for (int i = l; i <= r; i++) {
115
				if (ev[i].op == 1) {
116
					w[ev[i].x] = ev[i].w;
117
				}
118
			}
119
			cnt = 0, all = 0;
120
			for (int i = 1, id; i <= m; i++) {
121
				id = pr[i].second;
122
				if (vis[id] != tot) t_1[++cnt] = pr[i];
123
				else t_2[++all] = make_pair(w[id], id);
124
			}
125
			sort(t_2 + 1, t_2 + all + 1);
126
			merge(t_1 + 1, t_1 + cnt + 1, t_2 + 1, t_2 + all + 1, pr + 1);
127
		}
128
	}
129
}
130
131
int main() {
132
	__main__::main();
133
	return 0;
134
}

路灯

在路上了。

「Luogu P5445」路灯

题目描述

有 $n + 1$ 个位置,$n$ 条道路,第 $i$ 条连接第 $i$ 个位置和第 $i + 1$ 个位置。一开始时有些边是不可用的,有 $q$ 次操作,每次更改一条边的可用性,或者查询某两个点在历史中联通过多少个时刻。

数据范围:$n, q \le 3 \times 10^5$。

思路分析

将询问看作二维平面上的点,考虑一次修改的贡献。发现两点之间连通的时刻一定是形如:$t_1$ 时刻联通,$t_2$ 时刻断开,$t_3$ 时刻联通…… 的样子,所以我们考虑差分。设总时刻数为 $T$,当前时刻为 $t$。对于一次修改,如果是将两个区间联通,我们给这两个区间对应的矩形加上 $T - t$;否则它会把某个区间断开,我们给断开后分成的两个区间对应的矩形减去 $T - t$。另外,如果询问时如果两点联通,答案要额外地减去 $T - t$。

这样,我们就把问题转化成了一个带修改的二维数点的问题,使用 CDQ 分治套树状数组 / 树套树 / K-D Tree 即可。时间复杂度 $O(n\log^2 n)$ 或 $O(n \sqrt n)$。

代码实现

1
#include <bits/stdc++.h>
2
using namespace std;
3
4
typedef long long ll;
5
const int maxn = 3e5, maxm = maxn * 8, inf = 1e9;
6
int n, q, a[maxn + 3], m, tot;
7
char s[10];
8
set<pair<int, int> > S;
9
ll bit[maxn + 3], ans[maxn + 3];
10
11
struct event {
12
	int id, type, x, y, z;
13
	friend bool operator < (const event &a, const event &b) {
14
		return a.x < b.x;
15
	}
16
} ev[maxm + 3];
17
18
void add_ev(int type, int x, int y, int z) {
19
	++tot, ev[tot].id = tot, ev[tot].type = type;
20
	ev[tot].x = x, ev[tot].y = y, ev[tot].z = z;
21
}
22
23
void add(int u, int d, int l, int r, int t) {
24
	d++, r++;
25
	add_ev(1, u, l, t), add_ev(1, d, r, t);
26
	add_ev(1, u, r, -t), add_ev(1, d, l, -t);
27
}
28
29
void mod(int x, int y) {
30
	for (int i = x; i <= n; i += i & -i) {
31
		bit[i] += y;
32
	}
33
}
34
35
ll sum(int x) {
36
	ll y = 0;
37
	for (int i = x; i; i ^= i & -i) {
38
		y += bit[i];
39
	}
40
	return y;
41
}
42
43
void cdq(int l, int r) {
44
	if (l == r) {
45
		return;
46
	}
47
	int mid = (l + r) >> 1;
48
	cdq(l, mid), cdq(mid + 1, r);
49
	inplace_merge(ev + l, ev + mid + 1, ev + r + 1);
50
	for (int i = l; i <= r; i++) {
51
		if (ev[i].id <= mid && ev[i].type == 1) {
52
			mod(ev[i].y, ev[i].z);
53
		} else if (ev[i].id > mid && ev[i].type == 2) {
54
			ans[ev[i].z] += sum(ev[i].y);
55
		}
56
	}
57
	for (int i = l; i <= r; i++) {
58
		if (ev[i].id <= mid && ev[i].type == 1) {
59
			mod(ev[i].y, -ev[i].z);
60
		}
61
	}
62
}
63
64
int main() {
65
	scanf("%d %d", &n, &q);
66
	for (int i = 1; i <= n; i++) {
67
		scanf("%1d", &a[i]);
68
	}
69
	S.insert(make_pair(-inf, -inf)), S.insert(make_pair(inf, inf));
70
	for (int i = 1, j = 1; i <= n; i = j + 1, j = i) {
71
		if (!a[i]) continue;
72
		add(i, i, i, i, q);
73
		while (j < n && a[j + 1] == 1) {
74
			j++, add(i, j, j, j, q);
75
		}
76
		S.insert(make_pair(i, j));
77
	}
78
	set<pair<int, int> >::iterator it, ti;
79
	for (int i = 1, x, y, l, r; i <= q; i++) {
80
		scanf("%s", s + 1);
81
		if (s[1] == 't') {
82
			scanf("%d", &x);
83
			if (a[x]) {
84
				it = S.lower_bound(make_pair(x + 1, 0)), it--;
85
				l = it -> first, r = it -> second, S.erase(it);
86
				if (l < x) S.insert(make_pair(l, x - 1));
87
				if (x < r) S.insert(make_pair(x + 1, r));
88
				add(l, x, x, r, i - q);
89
			} else {
90
				it = S.lower_bound(make_pair(x + 1, 0)), l = r = x, ti = it, ti--;
91
				if (it -> first == x + 1) r = it -> second, S.erase(it);
92
				if (ti -> second == x - 1) l = ti -> first, S.erase(ti);
93
				S.insert(make_pair(l, r));
94
				add(l, x, x, r, q - i);
95
			}
96
			a[x] ^= 1;
97
		} else {
98
			scanf("%d %d", &x, &y), y--, ++m;
99
			bool flag = false;
100
			it = S.lower_bound(make_pair(x + 1, 0)), it--;
101
			if (it -> second >= y) ans[m] = i - q, flag = true;
102
			add_ev(2, x, y, m);
103
		}
104
	}
105
	cdq(1, tot);
106
	for (int i = 1; i <= m; i++) {
107
		printf("%lld\n", ans[i]);
108
	}
109
	return 0;
110
}