树分治

来源:互联网 发布:淘宝助手导入csv失败 编辑:程序博客网 时间:2024/05/16 00:46

参考:《分治算法在树的路径问题中的应用》漆子超

一、概述

树被定义为没有圈的联通图,具有以下几个性质:

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|1typedef 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)


参考:http://hzwer.com/3460.html

#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|1typedef 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;}






0 0
原创粉丝点击