参考:《分治算法在树的路径问题中的应用》漆子超
一、概述
树被定义为没有圈的联通图,具有以下几个性质:
1、在树中去掉一条边后所得的图是不连通的
2、在树中添加一条边后所得的图一定存在圈
3、树的每一顶点 u 和 v 之间有且仅有一条路径
分治算法在树结构上的运用,称之为树的分治算法
二、树的分治算法
1、基于点的分治
首先选取一个点将无根树转为有根树,再递归处理每一颗以根节点的儿子为根的子树
2、基于边的分治
在树中选取一条边,将原树分成两颗不相交的树,递归处理
3、
首先考虑如何选取点(边)
对于基于点的分治,我们选取一个点,要求将其删去后,结点最多的树的结点个数最小,这个点被称为“树的重心”
而基于边的分治,我们选取的边要满足所分离出来的两颗子树的结点个数尽量平均,这条边称为“中心边”
POJ 1655 Balancing Act
参考:http://blog.csdn.net/acdreamers/article/details/16905653
题意:
求树的重心的编号(如果有多个重心取编号最小)以及重心删除以后得到的最大子树的结点的个数
#include <iostream>
#include <cstdio>
#include <cstring>
#include <cstdlib>
#include <algorithm>
#include <queue>
#include <vector>
#include <stack>
#include <map>
#include <set>
#include <cmath>
#include <cctype>
#include <bitset>
#include <ctime>
using namespace std;
#define REP(i, n) for (int i = 0; i < (n); ++i)
#define lson low, mid, _id<<1
#define rson mid+1, high, _id<<1|1
typedef long long ll;
typedef unsigned long long ull;
typedef unsigned int uint;
typedef pair<int, int> Pair;
const ull mod = 1e9 + 7;
const int INF = 0x7fffffff;
const int maxn = 2e4 + 10;
int T, N, edge, a, b, ans_id, min_size;
int head[maxn], Next[2*maxn], to[2*maxn];
int son_size[maxn];
bool vis[maxn];
void Init(void);
void add_edge(int u, int v);
void dfs(int u);
int main()
{
#ifdef __AiR_H
freopen("in.txt", "r", stdin);
#endif // __AiR_H
scanf("%d", &T);
while (T--) {
scanf("%d", &N);
Init();
for (int i = 1; i < N; ++i) {
scanf("%d %d", &a, &b);
add_edge(a, b), add_edge(b, a);
}
dfs(1);
printf("%d %d\n", ans_id, min_size);
}
return 0;
}
void dfs(int u)
{
vis[u] = true, son_size[u] = 0;
int t = 0;
for (int i = head[u]; i != -1; i = Next[i]) {
int v = to[i];
if (!vis[v]) {
dfs(v);
son_size[u] += son_size[v] + 1;
t = max(t, son_size[v]+1);
}
}
t = max(t, N-son_size[u]-1);
if (t < min_size || (t == min_size && u < ans_id)) {
ans_id = u, min_size = t;
}
}
void add_edge(int u, int v)
{
to[edge] = v, Next[edge] = head[u], head[u] = edge++;
}
void Init(void)
{
memset(vis, false, sizeof(vis));
memset(head, -1, sizeof(head));
edge = 0, min_size = INF;
}
三、
例1、POJ 1741 Tree 树中点对统计
题意:
给定一棵 n(1 ≤ n ≤ 10000)个结点的边带权树,定义 dis(u, v) 为 u,v 两点间的最短路径长度,路径的长度定义为路径上所有边的权和
再给定一个 k(1 ≤ k ≤ 1e9),如果对于不同的两个结点 a,b,如果满足 dis(a, b) ≤ k,则称 (a, b) 为合法点对
求合法点对个数
解题思路:
记 dis[i] 表示点 i 到根节点的路径长度,belong[i] = x(x 为根节点的某个儿子,且结点 i 在以 x 为根的子树内)
那么我们要统计的就是:
满足 dis[i] + dis[j] ≤ k 且 belong[i] ≠ belong[j] 的 (i, j) 个数
= 满足 dis[i] + dis[j] ≤ k 的个数
- 满足 dis[i] + dis[j] ≤ k 且 belong[i] = belong[j] 的 (i, j) 个数
而对于这两个部分,都是要求满足 Ai + Aj ≤ k 的 (i, j) 的对数
将 A 排序后利用单调性我们容易得出一个 O(n) 的算法,所以我们可以用 O(nlogn) 的时间来解决这个问题
综上,此题使用树的分治算法时间复杂度为 O(n(logn)^2)
#include <iostream>
#include <cstdio>
#include <cstring>
#include <cstdlib>
#include <algorithm>
#include <queue>
#include <vector>
#include <stack>
#include <map>
#include <set>
#include <cmath>
#include <cctype>
#include <bitset>
#include <ctime>
using namespace std;
#define REP(i, n) for (int i = 0; i < (n); ++i)
#define lson low, mid, _id<<1
#define rson mid+1, high, _id<<1|1
typedef long long ll;
typedef unsigned long long ull;
typedef unsigned int uint;
typedef pair<int, int> Pair;
const ull mod = 1e9 + 7;
const int INF = 0x7fffffff;
const int maxn = 1e4 + 10;
int n, k, a, b, l, edge, root, ans, sum;
int head[maxn], Next[2*maxn], to[2*maxn], val[2*maxn];
bool vis[maxn];
int son_size[maxn], Max[maxn], dis_t[maxn], dis[maxn];
void Init(void);
void add_edge(int u, int v, int _val);
void get_root(int u, int _father);
void get_dis(int u, int _father);
int cal(int u, int now);
void slove(int u);
int main()
{
#ifdef __AiR_H
freopen("in.txt", "r", stdin);
#endif // __AiR_H
while (scanf("%d %d", &n, &k) != EOF && !(n == 0 && k == 0)) {
Init();
for (int i = 1; i < n; ++i) {
scanf("%d %d %d", &a, &b, &l);
add_edge(a, b, l), add_edge(b, a, l);
}
get_root(1, 0);
slove(root);
printf("%d\n", ans);
}
return 0;
}
void slove(int u)
{
ans += cal(u, 0);
vis[u] = true;
for (int i = head[u]; i != -1; i = Next[i]) {
int v = to[i];
if (!vis[v]) {
ans -= cal(v, val[i]);
sum = son_size[v] + 1;
root = 0;
get_root(v, root);
slove(root);
}
}
}
int cal(int u, int now)
{
dis_t[u] = now, dis[0] = 0;
get_dis(u, 0);
sort(dis+1, dis+dis[0]+1);
int ret = 0, low = 1, high = dis[0];
while (low < high) {
if (dis[low] + dis[high] <= k) {
ret += high - low, ++low;
} else {
--high;
}
}
return ret;
}
void get_dis(int u, int _father)
{
dis[++dis[0]] = dis_t[u];
for (int i = head[u]; i != -1; i = Next[i]) {
int v = to[i];
if (v == _father || vis[v]) {
continue;
}
dis_t[v] = dis_t[u] + val[i];
get_dis(v, u);
}
}
void get_root(int u, int _father) //求重心
{
son_size[u] = 0, Max[u] = 0;
for (int i = head[u]; i != -1; i = Next[i]) {
int v = to[i];
if (v == _father || vis[v]) {
continue;
}
get_root(v, u);
son_size[u] += son_size[v] + 1;
Max[u] = max(Max[u], son_size[v]+1);
}
Max[u] = max(Max[u], sum-son_size[u]-1);
if (Max[u] < Max[root]) {
root = u;
}
}
void add_edge(int u, int v, int _val)
{
to[edge] = v, Next[edge] = head[u], val[edge] = _val, head[u] = edge++;
}
void Init(void)
{
memset(vis, false, sizeof(vis));
memset(head, -1, sizeof(head));
edge = ans = root = 0;
Max[0] = INF;
sum = n;
}