http://hi.baidu.com/strongoier/item/fe47a4191c18a37c1009b515
【例1】POJ 1741 Tree
题目大意:
给出N(1 <= N <= 10000)个结点的树,求使得路径u -> v长度不超过k的点对(u, v)的个数。
算法分析:
首先我们对这棵树进行点分治,接下来考虑处理以root为根的子树。
记d[x]为x到root的距离,那么我们把子树的所有结点的d拉出来,即转化为了a[x] + a[y] <= k的(x, y)对数,这是可以通过排序后扫一遍解决的。
然后,这样可能出现重复的情况(x, y在root的同一儿子内部),那么我们只需要再对root的每个儿子进行同样的操作,但是是从之前的答案中减去。
时间复杂度O(Nlog^2N)。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
|
#include <algorithm>
#include <cstdio>
#include <cstring>
using
namespace
std;
const
int
MAXN = 11111, MAXM = 22222;
bool
mk[MAXM];
int
k, size, cur, tot, ans, a[MAXN], d[MAXN], s[MAXN], f[MAXN],
h[MAXN], p[MAXM], w[MAXM], nxt[MAXM];
inline
void
checkmax(
int
&x,
int
y) {
if
(y > x)
x = y;
}
inline
void
addedge(
int
x,
int
y,
int
z) {
p[tot] = y;
w[tot] = z;
nxt[tot] = h[x];
mk[tot] =
true
;
h[x] = tot++;
}
void
findroot(
int
x,
int
pre) {
s[x] = 1;
f[x] = 0;
for
(
int
k = h[x]; ~k; k = nxt[k])
if
(mk[k] && p[k] != pre) {
findroot(p[k], x);
s[x] += s[p[k]];
checkmax(f[x], s[p[k]]);
}
checkmax(f[x], size - s[x]);
if
(f[x] < f[cur])
cur = x;
}
void
dfs(
int
x,
int
pre) {
s[x] = 1;
a[++a[0]] = d[x];
for
(
int
k = h[x]; ~k; k = nxt[k])
if
(mk[k] && p[k] != pre) {
d[p[k]] = d[x] + w[k];
dfs(p[k], x);
s[x] += s[p[k]];
}
}
inline
int
calc(
int
x,
int
init) {
int
ret = a[0] = 0;
d[x] = init;
dfs(x, 0);
sort(a + 1, a + a[0] + 1);
for
(
int
l = 1, r = a[0]; l < r; )
if
(a[l] + a[r] <= k)
ret += r - l++;
else
--r;
return
ret;
}
void
work(
int
x) {
ans += calc(x, 0);
for
(
int
k = h[x]; ~k; k = nxt[k])
if
(mk[k]) {
mk[k ^ 1] =
false
;
ans -= calc(p[k], w[k]);
f[0] = size = s[p[k]];
findroot(p[k], cur = 0);
work(cur);
}
}
int
main() {
int
n;
while
(
scanf
(
"%d%d"
, &n, &k) && n) {
tot = ans = 0;
memset
(h, -1,
sizeof
h);
for
(
int
i = 1; i < n; ++i) {
int
x, y, z;
scanf
(
"%d%d%d"
, &x, &y, &z);
addedge(x, y, z);
addedge(y, x, z);
}
f[0] = size = n;
findroot(1, cur = 0);
work(cur);
printf
(
"%d\n"
, ans);
}
return
0;
}
|
【例2】BZOJ 2599 Race
题目大意:
给出N(1 <= N <= 200000)个结点的树,求长度等于K(1 <= K <= 1000000)的路径的最小边数。
算法分析:
首先我们对这棵树进行点分治,接下来考虑处理以root为根的子树。
为了方便,我们把更新答案和更新状态分离开。用d[i]表示长度为i的路径的最小边数。
当我们枚举root的某个儿子时,我们需要利用的是之前的儿子的d和当前路径长度合并从而更新答案。
做完以后,我们在把这个儿子路径上的状态更新到d中,从而在以后更新别人。
时间复杂度O(NlogN)。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
|
#include <cstdio>
#include <cstring>
const
int
MAXN = 222222, MAXM = 444444, MAXC = 1111111;
bool
mk[MAXM];
int
tot, cur, size, ans, now, n, m, v[MAXC], c[MAXC], s[MAXN], f[MAXN],
d[MAXN], e[MAXN], h[MAXN], p[MAXM], w[MAXM], nxt[MAXM];
inline
void
addedge(
int
x,
int
y,
int
z) {
p[tot] = y;
w[tot] = z;
nxt[tot] = h[x];
mk[tot] =
true
;
h[x] = tot++;
}
inline
void
checkmax(
int
&x,
int
y) {
if
(y > x)
x = y;
}
inline
void
checkmin(
int
&x,
int
y) {
if
(y < x)
x = y;
}
void
findroot(
int
x,
int
pre) {
s[x] = 1;
f[x] = 0;
for
(
int
k = h[x]; ~k; k = nxt[k])
if
(mk[k] && p[k] != pre) {
findroot(p[k], x);
s[x] += s[p[k]];
checkmax(f[x], s[p[k]]);
}
checkmax(f[x], size - s[x]);
if
(f[x] < f[cur])
cur = x;
}
void
dfs1(
int
x,
int
pre) {
if
(d[x] > m)
return
;
if
(v[m - d[x]] == now)
checkmin(ans, c[m - d[x]] + e[x]);
for
(
int
k = h[x]; ~k; k = nxt[k])
if
(mk[k] && p[k] != pre) {
d[p[k]] = d[x] + w[k];
e[p[k]] = e[x] + 1;
dfs1(p[k], x);
}
}
void
dfs2(
int
x,
int
pre) {
if
(d[x] > m)
return
;
if
(v[d[x]] != now) {
c[d[x]] = e[x];
v[d[x]] = now;
}
else
checkmin(c[d[x]], e[x]);
for
(
int
k = h[x]; ~k; k = nxt[k])
if
(mk[k] && p[k] != pre)
dfs2(p[k], x);
}
void
work(
int
x) {
v[0] = now = x + 1;
for
(
int
k = h[x]; ~k; k = nxt[k])
if
(mk[k]) {
d[p[k]] = w[k];
e[p[k]] = 1;
dfs1(p[k], x);
dfs2(p[k], x);
}
findroot(x, n);
for
(
int
k = h[x]; ~k; k = nxt[k])
if
(mk[k]) {
mk[k ^ 1] =
false
;
f[n] = size = s[p[k]];
findroot(p[k], cur = n);
work(cur);
}
}
int
main() {
scanf
(
"%d%d"
, &n, &m);
memset
(h, -1,
sizeof
h);
for
(
int
i = 1; i < n; ++i) {
int
x, y, z;
scanf
(
"%d%d%d"
, &x, &y, &z);
addedge(x, y, z);
addedge(y, x, z);
}
ans = f[n] = size = n;
findroot(0, cur = n);
work(cur);
printf
(
"%d\n"
, ans < n ? ans : -1);
return
0;
}
|
【例3】SPOJ FTOUR2
题目大意:
给出N(1 <= N <= 200000)个结点的树,其中有M个是黑结点,求包含不超过K个黑点的路径的最长长度。
算法分析:
首先我们对这棵树进行点分治,接下来考虑处理以root为根的子树。
倘若我们仍采用上一题的方法更新答案,由于更新的是一个区间,其状态总量是非常大的。
那么,我们考虑把root的儿子按路径上最多黑结点个数从大到小排序,用b[i](0 <= i <= K)表示过root的包含不超过i个黑结点的路径的最长长度。
设从某个儿子走下去路径最多黑结点个数为R,则我们用a[i](0 <= i <= R)表示从这个儿子走下去路径上有i个黑结点的最长长度。
注意更新的时候需要考虑[0, R]和[K - R, K]相交的情况。
第一步考虑利用之前的b和当前的a更新答案,即a[i] + b[K - i](0 <= i <= R)。
第二步考虑利用当前的a来更新b,我们发现,由于保证了R的单调减,需要更新的b的元素个数最多为R(再多以后的第一步不可能用到),因此我们只要更新b[i](K - R <= i <= K)即可。
分析复杂度,枚举每个儿子时,其R必然小于等于这个儿子的大小,因此对a, b的更新过程必然不超过以root为根的子树的总结点数。
因此,更新的总复杂度为O(NlogN)。此外,每个点最多在一次排序中,其复杂度仍为O(NlogN)。综上,算法整体时间复杂度O(NlogN)。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
|
#include <algorithm>
#include <cstdio>
#include <cstring>
using
namespace
std;
const
int
MAXN = 222222, MAXM = 444444;
bool
mk[MAXM], c[MAXN];
int
tot, cur, size, goal, ans, a[MAXN], b[MAXN], t[MAXN], d[MAXN], e[MAXN],
g[MAXN], s[MAXN], f[MAXN], h[MAXN], p[MAXM], w[MAXM], nxt[MAXM];
inline
void
addedge(
int
x,
int
y,
int
z) {
p[tot] = y;
w[tot] = z;
mk[tot] =
true
;
nxt[tot] = h[x];
h[x] = tot++;
}
inline
void
checkmax(
int
&x,
int
y) {
if
(y > x)
x = y;
}
void
findroot(
int
x,
int
pre) {
s[x] = 1;
g[x] = f[x] = 0;
for
(
int
k = h[x]; ~k; k = nxt[k])
if
(mk[k] && p[k] != pre) {
findroot(p[k], x);
checkmax(g[x], g[p[k]]);
s[x] += s[p[k]];
checkmax(f[x], s[p[k]]);
}
g[x] += c[x];
checkmax(f[x], size - s[x]);
if
(f[x] < f[cur])
cur = x;
}
bool
cmp(
int
x,
int
y) {
return
g[p[x]] > g[p[y]];
}
void
dfs(
int
x,
int
pre) {
if
(e[x] <= goal)
checkmax(a[e[x]], d[x]);
for
(
int
k = h[x]; ~k; k = nxt[k])
if
(mk[k] && p[k] != pre) {
e[p[k]] = e[x] + c[p[k]];
d[p[k]] = d[x] + w[k];
dfs(p[k], x);
}
}
void
work(
int
x) {
findroot(x, t[0] = 0);
for
(
int
k = h[x]; ~k; k = nxt[k])
if
(mk[k])
t[++t[0]] = k;
sort(t + 1, t + t[0] + 1, cmp);
for
(
int
i = max(goal - g[x], 0); i <= goal; ++i)
b[i] = 0;
goal -= c[x];
for
(
int
i = 1; i <= t[0]; ++i) {
int
k = t[i], hi = min(g[p[k]], goal), lo = goal - hi;
e[p[k]] = c[p[k]];
d[p[k]] = w[k];
for
(
int
j = 0; j <= hi; ++j)
a[j] = 0;
dfs(p[k], x);
for
(
int
j = 0; j <= hi; ++j)
checkmax(ans, b[goal - j] + a[j]);
int
mx = 0;
for
(
int
j = 0; j <= hi; ++j) {
checkmax(mx, a[j]);
if
(j >= lo)
checkmax(b[j], mx);
}
for
(
int
j = max(hi + 1, lo); j <= goal; ++j)
checkmax(b[j], mx);
}
goal += c[x];
for
(
int
k = h[x]; ~k; k = nxt[k])
if
(mk[k]) {
mk[k ^ 1] =
false
;
f[0] = size = s[p[k]];
findroot(p[k], cur = 0);
work(cur);
}
}
int
main() {
int
n, m;
scanf
(
"%d%d%d"
, &n, &goal, &m);
memset
(h, -1,
sizeof
h);
while
(m--) {
int
x;
scanf
(
"%d"
, &x);
c[x] =
true
;
}
for
(
int
i = 1; i < n; ++i) {
int
x, y, z;
scanf
(
"%d%d%d"
, &x, &y, &z);
addedge(x, y, z);
addedge(y, x, z);
}
f[0] = size = n;
findroot(1, cur = 0);
work(cur);
printf
(
"%d\n"
, ans);
return
0;
}
|
【例4】BZOJ 1758 重建计划
题目大意:
给出N(1 <= N <= 100000)个结点的树,求边数属于[L, U]的路径中路径长度除以边数的最大值。
算法分析:
首先我们对这棵树进行点分治,接下来考虑处理以root为根的子树。
由于求的是比值,我们很容易想到解决分数规划问题的一般思路——二分。
二分以后我们把边重赋权,然后求边数属于[L, U]的最长路径再判断即可。
这里可以借鉴上一题的思路,同样是排序来做,但更新答案不再是直接一路取max,而是使用单调队列来更新这个类似移动窗口的问题。
要注意的是,初值不能全设为负无穷,长度为0的路径是初始存在的(即直接以root为起点走下去)。
由于有二分,时间复杂度O(Nlog^2N)。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
|
#include <algorithm>
#include <cstdio>
#include <cstring>
using
namespace
std;
const
double
EPS = 1e-4;
const
int
MAXN = 111111, MAXM = 222222;
bool
mk[MAXM];
double
ans, l, r, mid, d[MAXN], a[MAXN], b[MAXN];
int
tot, size, cur, low, upp, e[MAXN], g[MAXN], t[MAXN], q[MAXN],
s[MAXN], f[MAXN], h[MAXN], p[MAXM], w[MAXM], nxt[MAXM];
inline
void
addedge(
int
x,
int
y,
int
z) {
p[tot] = y;
w[tot] = z;
mk[tot] =
true
;
nxt[tot] = h[x];
h[x] = tot++;
}
inline
void
checkmax(
int
&x,
int
y) {
if
(y > x)
x = y;
}
inline
void
checkmax(
double
&x,
double
y) {
if
(y > x)
x = y;
}
void
findroot(
int
x,
int
pre) {
s[x] = 1;
g[x] = f[x] = 0;
bool
t =
false
;
for
(
int
k = h[x]; ~k; k = nxt[k])
if
(mk[k] && p[k] != pre) {
t =
true
;
findroot(p[k], x);
checkmax(g[x], g[p[k]]);
s[x] += s[p[k]];
checkmax(f[x], s[p[k]]);
checkmax(r, w[k]);
if
(w[k] < l)
l = w[k];
}
g[x] += t;
checkmax(f[x], size - s[x]);
if
(f[x] < f[cur])
cur = x;
}
void
dfs(
int
x,
int
pre) {
if
(e[x] <= upp)
checkmax(a[e[x]], d[x]);
for
(
int
k = h[x]; ~k; k = nxt[k])
if
(mk[k] && p[k] != pre) {
e[p[k]] = e[x] + 1;
d[p[k]] = d[x] + w[k] - mid;
dfs(p[k], x);
}
}
bool
cmp(
int
x,
int
y) {
return
g[p[x]] > g[p[y]];
}
void
work(
int
x) {
l = 1e12;
r = 0;
findroot(x, t[0] = 0);
if
(s[x] <= low)
return
;
for
(
int
k = h[x]; ~k; k = nxt[k])
if
(mk[k])
t[++t[0]] = k;
sort(t + 1, t + t[0] + 1, cmp);
while
(r - l > EPS) {
mid = (l + r) / 2;
double
mx = -1e12;
for
(
int
i = max(upp - g[x], 0); i <= upp; ++i)
b[i] = i <= upp - low ? 0 : -1e12;
for
(
int
i = 1; i <= t[0]; ++i) {
int
k = t[i], hi = min(g[p[k]] + 1, upp), lo = upp - hi;
e[p[k]] = 1;
d[p[k]] = w[k] - mid;
for
(
int
j = 0; j <= hi; ++j)
a[j] = -1e12;
dfs(p[k], x);
for
(
int
j = 0; j <= hi; ++j)
checkmax(mx, b[upp - j] + a[j]);
int
f = 0, t = -1;
for
(
int
j = 0; j <= hi; ++j) {
for
(; f <= t && j - q[f] > upp - low; ++f);
for
(; f <= t && a[j] > a[q[t]]; --t);
q[++t] = j;
if
(f <= t && j >= lo)
checkmax(b[j], a[q[f]]);
}
for
(
int
j = max(hi + 1, lo); j <= upp; ++j) {
for
(; f <= t && j - q[f] > upp - low; ++f);
if
(f <= t)
checkmax(b[j], a[q[f]]);
}
}
if
(mx > 0)
l = mid;
else
r = mid;
}
checkmax(ans, l);
for
(
int
k = h[x]; ~k; k = nxt[k])
if
(mk[k]) {
mk[k ^ 1] =
false
;
f[0] = size = s[p[k]];
findroot(p[k], cur = 0);
work(cur);
}
}
int
main() {
int
n;
scanf
(
"%d%d%d"
, &n, &low, &upp);
memset
(h, -1,
sizeof
h);
for
(
int
i = 1; i < n; ++i) {
int
x, y, z;
scanf
(
"%d%d%d"
, &x, &y, &z);
addedge(x, y, z);
addedge(y, x, z);
}
f[0] = size = n;
findroot(1, cur = 0);
work(cur);
printf
(
"%.3lf\n"
, ans);
return
0;
}
|