美文网首页
POJ 1741 Tree 点分树题解

POJ 1741 Tree 点分树题解

作者: 失树 | 来源:发表于2017-10-31 19:01 被阅读0次

Openjudge原题链接

POJ原题链接

  • 题意
    输入树的各边及其长度,求路径小于等于k的点对数目

  • 题解
    目标:寻找长度小于等于k的路径
    首先选取一个点作为根节点,那么路径只有两种情况:经过根节点;不经过根节点
    如果不经过根节点,那么一定经过最小公共子树的根节点,划归为问题1,可分治求解。

  • 假定我们已经有了根节点和树的结构,那么只需要进行一次dfs,在dfs过程中求出每个节点到根节点的距离deep,以及计算出deep[x] + deep[y] <= k的pair数目,然后删除根节点,然后进入每一个子树重新计算新的deep并计算数目。但这样会重复计算。所以在第一次计算时应当减去属于同一棵子树的pair。

  • 但是,如果树退化成一条链,那么时间复杂度为O(N ^ 2)。所以需要每次巧妙地选择根节点使得最大的子树最小。即选择重心, 这样保证树的高度不超过O(logN)。

  • 更多解释参见代码注释

  • 总的时间复杂度为O(N(logN)^2)

  • 参考了ACMonster的解释和wwwiskey的解题思路

  • 参考了关于点分治的理解-chty

  • 参考了分治算法在树的路径问题中的应用——IOI2009中国国家集训队论文

  • AC代码如下

#if 0
要寻找长度小于等于k的路径
首先选取一个点作为根节点,那么路径只有两种情况:
1.经过根节点
2.不经过根节点
如果不经过根节点,那么一定经过最小公共子树的根节点,划归为问题1,可分治求解。

假定我们已经有了根节点和树的结构,那么只需要进行一次dfs,在dfs过程中求出每个节点到根节点的距离deep,以及
计算出deep[x] + deep[y] <= k的pair数目,然后删除根节点,然后进入每一个子树重新计算新的deep并计算数目。但这
样会重复计算。所以在第一次计算时应当减去属于同一棵子树的pair。

但是,如果树退化成一条链,那么时间复杂度为O(N ^ 2)。所以需要每次巧妙地选择根节点使得最大的子树最小。即选择
重心, 这样保证树的高度不超过O(logN)。


以下是选择重心的源码。
void getroot(int x, int fa)//x表示当前结点,fa表示x的父结点
{
    son[x] = 1; F[x] = 0;//F数组记录以x为根的最大子树的大小
    for (int i = Link[x]; i; i = e[i].next)
        if (e[i].y != fa && !vis[e[i].y])//避免陷入死循环
        {
            getroot(e[i].y, x);//得到子结点信息
            son[x] += son[e[i].y];//计算x结点大小
            F[x] = max(F[x], son[e[i].y]);//更新F数组
        }
    F[x] = max(F[x], sum - son[x]);//sum表示当前树的大小,因为以x为根的情况还要考虑以x的父亲为根的子树大小。
    if (F[x]<F[root])root = x;//更新当前根
}

以下是删除根节点后,递归处理每一棵子树(此时还不是子树,是联通块,需要找新的根节点)的源码
int solve(int x)
{
    vis[x] = 1;//将当前点标记
    for (int i = Link[x]; i; i = e[i].next)
        if (!vis[e[i].y])
        {
            root = 0;//初始化根  
            sum = e[i].y;//初始化sum
            getroot(x, 0);//找连通块的根
            solve(e[i].y);//递归处理下一个连通块
        }
}
int main()
{
    build();//建树
    sum = f[0] = n;//初始化sum和f[0]
    root = 0;//初始化root
    getroot(1, 0);//找根
    solve(root);//点分治
}
#endif



#include<iostream>
#include<algorithm>
#include<queue>
#include<vector>
#include<cstring>
#define MAXN 10005
using namespace std;
struct Edge {
    int v, l;//v表示连接到的下一个节点,l表示长度
    Edge(int v_, int l_) :v(v_), l(l_) {};
};
vector<Edge> g[MAXN];//用变长数组存储图
vector<int> dep;    //用来计算在某图下,以某个节点作为根节点时的各子节点的deep,它不需要知道具体是哪个子节点
int dist[MAXN];     //
int n, k;
int vis[MAXN];
int f[MAXN];//存储在当前图下,以某个节点作为根节点时,得到的最大子树的大小
int root;   //当前找到的根节点
int ans;    //最后要求的配对数目
int s[MAXN];//s for size,记录在当前图下每个节点的子树大小之和,包括自己
int totalNodes;//n-删除的点的个数
void getroot(int now, int fa) {
    int u;
    //s的存在是为了累加,然后得出父节点等所有祖先构成子树的大小
    s[now] = 1;//自己
    f[now] = 0;//存储每个节点的最大子树
    for (int i = 0; i < g[now].size(); i++) {//不会陷入死循环的原因之一:树上存在叶节点(只与父节点相连)
        u = g[now][i].v;//子节点
        if (u != fa && !vis[u]) {//不是父节点且未被切除,如果不排除父节点会陷入死循环
            getroot(u, now);//递归地找到子树的最大子树和子树的大小
            s[now] += s[u]; //加入子树的大小
            f[now] = max(f[now], s[u]);//找最大子树
        }
    }
    f[now] = max(f[now], totalNodes - s[now]);//把父节点等所有祖先当作一棵子树
    if (f[now] < f[root]) root = now;//找最大子树最小的根节点
}
void getdep(int now, int fa) {//在某个图某个根节点下求deep,天然的深度是dist[now]
    int u;
    dep.push_back(dist[now]);
    s[now] = 1;
    for (int i = 0; i < g[now].size(); i++) {
        u = g[now][i].v;
        if (u != fa && !vis[u]) {
            dist[u] = dist[now] + g[now][i].l;
            getdep(u, now);
            s[now] += s[u];
        }
    }
}
int calc(int now, int len) {//在减去属于同一棵子树的时候,len是这个子树到原根节点的距离
                            //我们把这个距离考虑进来,
    dep.clear();
    dist[now] = len;//以dist[now]作为基础求deep
    getdep(now, 0);//在当前图当前根节点下,计算每个子节点的deep,放入dep数组
    sort(dep.begin(), dep.end());
    int cnt = 0;//统计数目
    int l = 0, r = dep.size() - 1;
    /*
    用l表示左指针,r表示右指针,i从左向右遍历。
    如果dep[i]+dep[j]≤k,则点对(i,t)(i<t≤j)都符合题意,将r-l加入答案中,并且l++;否则r--。
    */
    while (l < r) {
        if (dep[l] + dep[r] <= k) {
            cnt += r - l;
            l++;
        }
        else r--;
    }
    return cnt;
}
void work(int now) {//以now作为根节点,在当前的图下计算满足题意的pair个数
    int u;
    ans += calc(now, 0);//以now作为根节点,在当前的图下所有deep[i]+deep[j]<=k的(i,j)的数目
                        //然后减去属于同一棵子树的(i,j),于是得到了经过根节点的所有(i,j)路径
    vis[now] = true;//剪掉根节点,得到不连通的几棵子树
    for (int i = 0; i < g[now].size(); i++) {
        u = g[now][i].v;
        if (!vis[u]) {
            ans -= calc(u, g[now][i].l);//减去属于同一棵子树的(i,j)
            f[0] = totalNodes = s[u];//考虑连通的某棵子树,其总的大小
            root = 0;
            getroot(u, 0);//找到新的根节点
            work(root);//递归地增加不经过当前根节点,但是经过新的根节点的路径数目
        }
    }
}
int main() {
    while (cin >> n >> k) {
        if (!n && !k) break;
        for (int i = 0; i <= n; i++)
            g[i].clear();
        memset(vis, 0, sizeof(vis));
        int u, v, l;
        for (int i = 1; i < n; i++) {//读入n-1条边
            cin >> u >> v >> l;
            g[u].push_back(Edge(v, l));
            g[v].push_back(Edge(u, l));
        }
        f[0] = n;
        root = 0;
        totalNodes = n;
        getroot(1, 0);
        ans = 0;
        work(root);
        cout << ans << endl;
    }
}

相关文章

  • POJ 1741 Tree 点分树题解

    Openjudge原题链接 POJ原题链接 题意输入树的各边及其长度,求路径小于等于k的点对数目 题解目标:寻找长...

  • POJ 1741 Tree

    Description Give a tree with n vertices,each edge has a l...

  • POJ 1741 Tree

    难度 尚未评定Description给定一颗有个节点的树,每条边有一个权值。树上两个节点和之间的路径长度就是路径上...

  • POJ_1741 男人八题

    1.题目相关 标签:树分治 题目地址:http://poj.org/problem?id=1741 题目大意:见论...

  • (动态)点分治

    POJ-1741(带边权 && 边权可以为负值的树) 复杂度: O(nlog²n) Distance in Tre...

  • CUIT MAGICIAN UNION 2017 TRAININ

    第一题 Expanding Rods POJ 1905 题解算法:二分搜索 第二题 Frogger POJ 22...

  • 计算几何

    极限 POJ 1981: Circle and Points POJ 1418: Viva Confetti题解链...

  • Chapter16—计算几何学

    1. 题目列表 POJ2031(点与点的距离 + 最小生成树) POJ1039(线段相交判断,交点的计算) POJ...

  • POJ 3321 Apple Tree 树状数组题解

    原题链接 Apple Tree 题意 一棵多叉树每个结点有一个编号和一个值,在已知树的结构的情况下,进行两种操作。...

  • 数据结构-二叉树、BST(二分搜索树)

    章节 tree结构简介 二叉树详解 二分搜索树 - Binary Search Tree 1 tree结构简介 t...

网友评论

      本文标题:POJ 1741 Tree 点分树题解

      本文链接:https://www.haomeiwen.com/subject/xhxipxtx.html