树分治

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

一、概述

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

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)


参考: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|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;
}






评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值