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

0%

「学习笔记」长链剖分

简介

长链剖分是一种树链剖分,与重链剖分不同的是,每个点的关键儿子是它向下高度最大的儿子。

长链剖分有许多优美的性质。例如,某个点的 $k$ 级祖先所在链的边数一定不小于 $k$;任意两点之间只会跨过 $\sqrt n$ 条链。另外,长链剖分还可以在线性的时间内解决状态规模为子树高度的许多 dp。

k 级祖先

「Vijos 巴蜀中学」lxhgww 的奇思妙想

题目描述

给定一棵 $n$ 个点的树,$q$ 次询问点 $x$ 的 $k$ 级祖先。

强制在线,复杂度要求 $O(n \log n) - O(1)$。

思路分析

考虑利用简介中提到的一个性质:“某个点的 $k$ 级祖先所在链的长度一定不小于 $k$”。我们先用倍增预处理出每个点向上跳 $2^t$ 步会到达哪个点。考虑 $k$ 在二进制下的最高位 $b$,记 $x$ 的 $2^b$ 级祖先为 $y$,然后我们只要求 $y$ 的 $k - 2^b$ 级祖先。首先有 $y$ 所在链的长度不小于 $2^b$,然后发现 $k - 2^b < 2^b$,所以 $y$ 所在链的长度一定超过 $k - 2^b$。我们只需要处理出每个链顶向上跳不超过链长步会到达哪些点,存进 std::vector 中即可 $O(1)$ 查询 $y$ 的 $k - 2^b$ 级祖先。时间复杂度 $O(n \log n) - O(1)$。

代码实现

这题 RE on #6, 7, 8 实在调不出来了,代码仅供参考。

1
#include <bits/stdc++.h>
2
using namespace std;
3
4
const int maxn = 3e5, logn = 18;
5
int n, m, dep[maxn + 3], mx[maxn + 3], ch[maxn + 3], fa[maxn + 3][logn + 3];
6
int top[maxn + 3], bot[maxn + 3], len[maxn + 3], ans, lg[maxn + 3];
7
vector<int> G[maxn + 3], S[maxn + 3];
8
9
void dfs(int u, int pa = 0) {
10
	for (int i = 1; (fa[u][i] = fa[fa[u][i - 1]][i - 1]); i++);
11
	for (int i = 0, v; i < G[u].size(); i++) {
12
		v = G[u][i];
13
		if (v == pa) continue;
14
		fa[v][0] = u;
15
		dep[v] = dep[u] + 1;
16
		dfs(v, u);
17
		if (mx[v] + 1 > mx[u]) {
18
			mx[u] = mx[v] + 1, ch[u] = v;
19
		}
20
	}
21
}
22
23
void solve(int u, int pa = 0, int t = 1) {
24
	top[u] = t;
25
	for (int i = 0, v; i < G[u].size(); i++) {
26
		v = G[u][i];
27
		if (v == pa || v == ch[u]) continue;
28
		solve(v, u, v);
29
	}
30
	if (ch[u]) {
31
		len[top[u]]++;
32
		solve(ch[u], u, t);
33
	} else {
34
		bot[top[u]] = u;
35
	}
36
	S[top[u]].push_back(u);
37
	if (u == t) {
38
		for (int i = 1, x = u; i <= len[u]; i++) {
39
			S[u].push_back(x = fa[x][0]);
40
		}
41
	}
42
}
43
44
int query(int x, int k) {
45
	if (k == 0) {
46
		return x;
47
	} else if (k > dep[x]) {
48
		return 0;
49
	}
50
	int t = lg[k];
51
	x = fa[x][t], k -= 1 << t;
52
	return S[top[x]][dep[bot[top[x]]] - dep[x] + k];
53
}
54
55
int main() {
56
	scanf("%d", &n);
57
	for (int i = 2; i <= n; i++) {
58
		lg[i] = lg[i >> 1] + 1;
59
	}
60
	for (int i = 1, u, v; i < n; i++) {
61
		scanf("%d %d", &u, &v);
62
		G[u].push_back(v), G[v].push_back(u);
63
	}
64
	dfs(1);
65
	solve(1);
66
	scanf("%d", &m);
67
	for (int x, k; m --> 0; ) {
68
		scanf("%d %d", &x, &k);
69
		x ^= ans, k ^= ans;
70
		printf("%d\n", ans = query(x, k));
71
	}
72
	return 0;
73
}

贪心

「BZOJ 3252」攻略

题目描述

给定点带权的点数为 $n$ 的树,要选出 $k$ 条包含 $1$ 的祖先后代链,使得它们并的点权和最大。

$n \le 2 \times 10^5, 1 \le a_i < 2^{31}$。

思路分析

考虑带权的长链剖分,剖出的链取前 $k$ 条加起来即可。时间复杂度 $O(n \log n)$。

代码实现

1
#include <bits/stdc++.h>
2
using namespace std;
3
4
typedef long long ll;
5
const int maxn = 2e5;
6
int n, k, a[maxn + 3], id[maxn + 3];
7
ll mx[maxn + 3];
8
vector<int> G[maxn + 3];
9
10
void dfs(int u, int pa = 0) {
11
	ll x = 0;
12
	for (int i = 0, v; i < G[u].size(); i++) {
13
		v = G[u][i];
14
		if (v == pa) continue;
15
		dfs(v, u);
16
		if (mx[id[v]] + a[u] > x) {
17
			x = mx[id[v]] + a[u], id[u] = id[v];
18
		}
19
	}
20
	if (id[u]) {
21
		mx[id[u]] += a[u];
22
	} else {
23
		mx[u] = a[u], id[u] = u;
24
	}
25
}
26
27
int main() {
28
	scanf("%d %d", &n, &k);
29
	for (int i = 1; i <= n; i++) {
30
		scanf("%d", &a[i]);
31
	}
32
	for (int i = 1, u, v; i < n; i++) {
33
		scanf("%d %d", &u, &v);
34
		G[u].push_back(v), G[v].push_back(u);
35
	}
36
	dfs(1);
37
	sort(mx + 1, mx + n + 1);
38
	reverse(mx + 1, mx + n + 1);
39
	ll ans = 0;
40
	for (int i = 1; i <= k; i++) {
41
		ans += mx[i];
42
	}
43
	printf("%lld\n", ans);
44
	return 0;
45
}

模版题

「CF 1009F」Dominant Indices

题目描述

记 $f(u, i)$ 为 $u$ 往下走 $i$ 步到达的点数,给定 $n$ 个点的树,对于每个点求出序列 $[f(u, 0), f(u, 1), \cdots]$ 中的最大值的最小下标。

数据范围:$n \le 10^6$。

思路分析

考虑长链剖分求 $f$,对于一个点,先继承特殊儿子的答案,然后对于其他儿子暴力更新即可。时间复杂度 $O(n)$。

代码实现

std::vector 实现,代码更短哦!

1
#include <bits/stdc++.h>
2
using namespace std;
3
4
const int maxn = 1e6;
5
int n, mx[maxn + 3], ch[maxn + 3], id[maxn + 3], ans[maxn + 3], res[maxn + 3];
6
vector<int> G[maxn + 3], f[maxn + 3];
7
8
void dfs(int u, int pa = 0) {
9
	for (int i = 0, v; i < G[u].size(); i++) {
10
		v = G[u][i];
11
		if (v == pa) continue;
12
		dfs(v, u);
13
		if (mx[v] + 1 > mx[u]) {
14
			mx[u] = mx[v] + 1, ch[u] = v;
15
		}
16
	}
17
	int x = id[u] = ch[u] ? id[ch[u]] : u;
18
	ans[u] = ch[u] ? ans[ch[u]] + 1 : 0, res[u] = ch[u] ? res[ch[u]] : 1;
19
	f[x].push_back(1);
20
	if (res[u] == 1) {
21
		ans[u] = 0;
22
	}
23
	for (int i = 0, v; i < G[u].size(); i++) {
24
		v = G[u][i];
25
		if (v == pa || v == ch[u]) continue;
26
		int y = id[v];
27
		for (int j = 0; j < f[y].size(); j++) {
28
			int p = f[x].size() - f[y].size() + j - 1;
29
			f[x][f[x].size() - f[y].size() + j - 1] += f[y][j];
30
			int id = f[x].size() - p - 1, val = f[x][p];
31
			if (val > res[u] || (val == res[u] && id < ans[u])) {
32
				ans[u] = id, res[u] = val;
33
			}
34
		}
35
	}
36
}
37
38
int main() {
39
	scanf("%d", &n);
40
	for (int i = 1, u, v; i < n; i++) {
41
		scanf("%d %d", &u, &v);
42
		G[u].push_back(v), G[v].push_back(u);
43
	}
44
	dfs(1);
45
	for (int i = 1; i <= n; i++) {
46
		printf("%d\n", ans[i]);
47
	}
48
	return 0;
49
}

重建计划

「WC 2010」重建计划

题目描述

给定边带权的 $n$ 个点的树,选出一条长度在 $[L, U]$ 内的链,使得边权的平均值最小。

数据范围:$n \le 10^5$。

思路分析

首先可以想到二分答案,问题变成链的最大权值和是否超过 $0$。考虑长链剖分,设 $f(u, i)$ 表示 $u$ 的子树内以 $u$ 为端点的权值最大的长度为 $i$ 的路径长度。我们考虑使用线段树维护 $f$,从特殊儿子继承时只需要区间加,从其他儿子先更新答案(线段树区间最大值),后更新 $f$(暴力)即可。时间复杂度 $O(n \log^2 n)$。

代码实现

注意这个题不能用 std::vector,而是要使用内存分配的技巧。对于每一条链,我们改变 dfs 的顺序,使得每条链的 dfs 序连续。这样开一个全局的线段树,一条链上的一段就可以对应线段树的一个区间了。推荐简单题使用 std::vector,难题使用内存分配的方法。(因为难题用 std::vector 会写自闭的,详见 HOT Hotels)

1
#include <bits/stdc++.h>
2
using namespace std;
3
4
typedef double db;
5
const int maxn = 1e5, maxm = 1 << 18;
6
const db inf = 1e18;
7
int n, L, U, mxd[maxn + 3], ch[maxn + 3], top[maxn + 3], bot[maxn + 3];
8
int cnt, wei[maxn + 3], dfn[maxn + 3];
9
db val[maxn + 3], ans, mx[maxm + 3], tag[maxm + 3];
10
vector<int> G[maxn + 3], W[maxn + 3];
11
12
void add(int u, int v, int w) {
13
	G[u].push_back(v), W[u].push_back(w);
14
}
15
16
void dfs_1(int u, int pa = 0) {
17
	for (int i = 0, v, w; i < G[u].size(); i++) {
18
		v = G[u][i], w = W[u][i];
19
		if (v == pa) continue;
20
		wei[v] = w;
21
		dfs_1(v, u);
22
		if (mxd[v] + 1 > mxd[u]) {
23
			mxd[u] = mxd[v] + 1, ch[u] = v;
24
		}
25
	}
26
}
27
28
void dfs_2(int u, int pa = 0, int t = 1) {
29
	dfn[u] = ++cnt;
30
	top[u] = t;
31
	if (ch[u]) {
32
		dfs_2(ch[u], u, t);
33
		bot[u] = bot[ch[u]];
34
	} else {
35
		bot[u] = u;
36
	}
37
	for (int i = 0, v; i < G[u].size(); i++) {
38
		v = G[u][i];
39
		if (v == pa || v == ch[u]) continue;
40
		dfs_2(v, u, v);
41
	}
42
}
43
44
void upd(db &x, db y) {
45
	x < y ? x = y : 0;
46
}
47
48
#define ls (x << 1)
49
#define rs (ls | 1)
50
#define mid ((l + r) >> 1)
51
52
void build(int x, int l, int r) {
53
	mx[x] = 0, tag[x] = 0;
54
	if (l == r) {
55
		return;
56
	}
57
	build(ls, l, mid);
58
	build(rs, mid + 1, r);
59
}
60
61
void push_down(int x) {
62
	mx[ls] += tag[x], tag[ls] += tag[x];
63
	mx[rs] += tag[x], tag[rs] += tag[x];
64
	tag[x] = 0;
65
}
66
67
void maintain(int x) {
68
	mx[x] = max(mx[ls], mx[rs]);
69
}
70
71
void chk_max(int x, int l, int r, int y, db z) {
72
	if (l == r) {
73
		mx[x] = max(mx[x], z);
74
		return;
75
	}
76
	push_down(x);
77
	if (y <= mid) {
78
		chk_max(ls, l, mid, y, z);
79
	} else {
80
		chk_max(rs, mid + 1, r, y, z);
81
	}
82
	maintain(x);
83
}
84
85
void modify(int x, int l, int r, int lx, int rx, db y) {
86
	if (l >= lx && r <= rx) {
87
		mx[x] += y, tag[x] += y;
88
		return;
89
	}
90
	push_down(x);
91
	if (lx <= mid) {
92
		modify(ls, l, mid, lx, rx, y);
93
	}
94
	if (rx > mid) {
95
		modify(rs, mid + 1, r, lx, rx, y);
96
	}
97
	maintain(x);
98
}
99
100
db query(int x, int l, int r, int lx, int rx) {
101
	if (l >= lx && r <= rx) {
102
		return mx[x];
103
	}
104
	push_down(x);
105
	db ret = -inf;
106
	if (lx <= mid) {
107
		upd(ret, query(ls, l, mid, lx, rx));
108
	}
109
	if (rx > mid) {
110
		upd(ret, query(rs, mid + 1, r, lx, rx));
111
	}
112
	return ret;
113
}
114
115
db get(int x) {
116
	return query(1, 1, n, x, x);
117
}
118
119
#undef ls
120
#undef rs
121
#undef mid
122
123
void solve(int u, int pa = 0) {
124
	int x = dfn[u];
125
	if (ch[u]) {
126
		solve(ch[u], u);
127
		modify(1, 1, n, x + 1, dfn[bot[u]], val[ch[u]]);
128
		int lft = x + L, rht = min(dfn[bot[u]], x + U);
129
		if (lft <= rht) {
130
			upd(ans, query(1, 1, n, lft, rht));
131
		}
132
	}
133
	for (int i = 0, v; i < G[u].size(); i++) {
134
		v = G[u][i];
135
		if (v == pa || v == ch[u]) continue;
136
		solve(v, u);
137
		int y = dfn[v];
138
		for (int j = 0; j <= dfn[bot[v]] - y; j++) {
139
			int lft = max(x + L - 1 - j, x), rht = min(x + U - 1 - j, dfn[bot[u]]);
140
			if (lft <= rht) {
141
				upd(ans, query(1, 1, n, lft, rht) + get(y + j) + val[v]);
142
			}
143
		}
144
		for (int j = 0; j <= dfn[bot[v]] - y; j++) {
145
			chk_max(1, 1, n, dfn[u] + j + 1, get(y + j) + val[v]);
146
		}
147
	}
148
}
149
150
bool check(db x) {
151
	build(1, 1, n);
152
	for (int i = 2; i <= n; i++) {
153
		val[i] = wei[i] - x;
154
	}
155
	ans = -inf;
156
	solve(1);
157
	return ans >= 0;
158
}
159
160
int main() {
161
	scanf("%d %d %d", &n, &L, &U);
162
	for (int i = 1, u, v, w; i < n; i++) {
163
		scanf("%d %d %d", &u, &v, &w);
164
		add(u, v, w), add(v, u, w);
165
	}
166
	dfs_1(1);
167
	dfs_2(1);
168
	db l = 0, r = 1e6, mid;
169
	while (l + 1e-4 < r) {
170
		mid = (l + r) / 2;
171
		if (check(mid)) {
172
			l = mid;
173
		} else {
174
			r = mid;
175
		}
176
	}
177
	printf("%.3lf\n", l);
178
	return 0;
179
}

HOT Hotels

「POJ 2014」HOT Hotels

题目描述

给定 $n$ 个点的树,求有多少点的三元组满足它们两两之间距离相等。

数据范围:$n \le 10^5$。

思路分析

发现三元组肯定有一个中心到三个点距离相等。记 $f(u, i)$ 表示 $u$ 子树内和它距离为 $i$ 的点数,$g(u, i)$ 表示 $u$ 子树内有多少点对满足它们到 LCA 的距离都为 $t$ 且 LCA 到 $u$ 的距离为 $t - i$(也就是说在上面加上长度为 $i$ 的链后可以形成方案)。那么转移方程如下:

1
for(int i = 0;i <= n;i ++) ans += g[x][i] * (i == 0 ? 0 : f[to][i-1]) + g[to][i+1] * f[x][i];
2
for(int i = 0;i <= n;i ++) g[x][i] += f[x][i] * (i == 0 ? 0 : f[to][i-1]) + g[to][i+1];
3
for(int i = 1;i <= n;i ++) f[x][i] += f[to][i-1];

(转自 Treaker 的博客

注意 $f(u, 0)$ 的初值为 $1$,dfs 完 $u$ 时还要让 $\text{ans} \leftarrow \text{ans} + g(u, 0)$。

那么使用长链剖分优化即可。时间复杂度 $O(n)$。

代码实现

std::vector 写的,由于 $g$ 数组转移的特殊,下标的变化很玄学,细节比较多。使用分配内存的方法就可以减少细节量。

1
// f[x][i] = p[x][f[x].size() - i - 1]
2
// g[x][i] = q[x][i - mx(x) + 1]
3
// p[x][i] = f[x][f[x].size() - i - 1]
4
// q[x][i] = g[x][i + mx(x) - 1]
5
6
#include <bits/stdc++.h>
7
using namespace std;
8
9
typedef long long ll;
10
const int maxn = 1e5;
11
int n, tot, ch[maxn + 3], mx[maxn + 3], id[maxn + 3];
12
ll ans;
13
vector<int> G[maxn + 3];
14
vector<ll> f[maxn + 3], g[maxn + 3];
15
16
void dfs(int u, int pa = 0) {
17
	for (int i = 0, v; i < G[u].size(); i++) {
18
		v = G[u][i];
19
		if (v == pa) continue;
20
		dfs(v, u);
21
		if (mx[v] + 1 > mx[u]) {
22
			mx[u] = mx[v] + 1, ch[u] = v;
23
		}
24
	}
25
	int x = id[u] = ch[u] ? id[ch[u]] : ++tot;
26
	f[x].push_back(1);
27
	if (ch[u]) {
28
		for (int t = 0; t < 2; t++) {
29
			g[x].push_back(0);
30
		}
31
	}
32
	for (int i = 0, v; i < G[u].size(); i++) {
33
		v = G[u][i];
34
		if (v == pa || v == ch[u]) continue;
35
		int y = id[v];
36
		for (int j = 1; j < f[x].size(); j++) if (j + mx[v] < g[y].size()) {
37
			ans += f[x][f[x].size() - j - 1] * g[y][j + mx[v]];
38
		}
39
		for (int j = 0; j < f[y].size(); j++) {
40
			ans += g[x][j + mx[u]] * f[y][f[y].size() - j - 1];
41
		}
42
		if (ch[v]) for (int j = 0; j <= mx[v]; j++) {
43
			g[x][j - 1 + mx[u] - 1] += g[y][j + mx[v] - 1];
44
		}
45
		for (int j = 0; j < f[y].size(); j++) {
46
			g[x][j + mx[u]] += f[x][f[x].size() - (j + 1) - 1] * f[y][f[y].size() - j - 1];
47
		}
48
		for (int j = 0; j < f[y].size(); j++) {
49
			f[x][j + f[x].size() - f[y].size() - 1] += f[y][j];
50
		}
51
	}
52
	if (ch[u]) {
53
		ans += g[x][mx[u] - 1];
54
		if (mx[u] - 2 >= 0) {
55
			g[x][mx[u] - 2] = 0;
56
		} 
57
	}
58
}
59
60
int main() {
61
	scanf("%d", &n);
62
	for (int i = 1, u, v; i < n; i++) {
63
		scanf("%d %d", &u, &v);
64
		G[u].push_back(v), G[v].push_back(u);
65
	}
66
	dfs(1);
67
	printf("%lld\n", ans);
68
	return 0;
69
}