poj 1741 Tree(树的点分治)

poj 1741 Tree(树的点分治)

给出一个n个结点的树和一个整数k,问有多少个距离不超过k的点对。

首先对于一个树中的点对,要么经过根结点,要么不经过。所以我们可以把经过根节点的符合点对统计出来。接着对于每一个子树再次运算。如果不用点分治的技巧,时间复杂度可能退化成\(O(n^2)\)(链)。如果对于子树重新选根,找到树的重心,就一定可以保证时间复杂度在\(O(nlogn)\)内。

具体技巧是:首先选出树的重心,将重心视为根。接着计算出每个结点的深度,以此统计答案。由于子树中可能出现重复情况,需要在子树中相应的减去一部分ans。具体实现在代码中。

#include <cstdio>
#include <cstring>
#include <algorithm>
using namespace std;

const int maxn=1e4+5;

struct Graph{
    struct Edge{
        int to, next, v; Graph *bel;
        Edge& operator ++(){
            return *this=bel->edge[next]; }
    }edge[maxn*2];
    int cntedge, fir[maxn];
    void addedge(int x, int y, int v){
        Edge &e=edge[++cntedge];
        e.to=y; e.next=fir[x]; e.v=v;
        fir[x]=cntedge; e.bel=this;
    }
    Edge& getlink(int x){ return edge[fir[x]]; }
    void RESET(){ cntedge=0; memset(fir, 0, sizeof(fir)); }
}g;

int n, k, size[maxn], w[maxn], dep[maxn];
int cnt[maxn], tail;
bool done[maxn];

//获取子树大小
int getsize(int now, int par, int num){
    size[now]=0; w[now]=0;
    Graph::Edge e=g.getlink(now);
    for (; e.to; ++e){
        if (e.to==par||done[e.to]) continue;
        size[now]+=getsize(e.to, now, num);
        w[now]=max(w[now], size[e.to]);
    }
    ++size[now]; w[now]=max(w[now], num-size[now]);
    return size[now];
}

//获取根的位置
int getroot(int now, int par){
    Graph::Edge e=g.getlink(now);
    int root=now, tmp=now;
    for (; e.to; ++e){
        if (e.to==par||done[e.to]) continue;
        tmp=getroot(e.to, now);
        if (w[tmp]<w[root]) root=tmp; //mdzzle
    }
    return root;
}

//获取子树中结点深度,并创造深度数组
void getdep(int now, int par, int step){
    Graph::Edge e=g.getlink(now);
    for (; e.to; ++e)
        if (e.to!=par&&!done[e.to])
        getdep(e.to, now, step+e.v);
    dep[now]=step;
    cnt[tail++]=step;
}

//通过深度数组统计和小于等于k的数对
int getans(int k, int tail){
    sort(cnt, cnt+tail);
    --tail; int ans=0;
    for (int l=0; l<tail; ++l){
        while (cnt[l]+cnt[tail]>k&&l<tail) --tail;
        ans+=tail-l;
    }
    return ans;
}

int solve(int now, int num){
    getsize(now, 0, num); //获取当前树的子树大小
    if (size[now]==1) return 0;
    now=getroot(now, 0); //凭借子树大小找到重心
    tail=0; //把深度数组复原
    getdep(now, 0, 0); //获取每个结点的深度,构建深度数组
    //获取答案,别忘记减掉重复的点对
    int ans=getans(k, tail);
    done[now]=true; //堵住当前点
    Graph::Edge e=g.getlink(now);
    for (; e.to; ++e) if (!done[e.to]){
        tail=0; getdep(e.to, 0, 0);
        ans-=getans(k-e.v*2, tail);
        ans+=solve(e.to, size[e.to]);
    }
    return ans;
}

int main(){
    while (~scanf("%d%d", &n, &k)&&n&&k){
        int t1, t2, t3; g.RESET();
        for (int i=1; i<=n; ++i) done[i]=false;
        for (int i=1; i<n; ++i){
            scanf("%d%d%d", &t1, &t2, &t3);
            g.addedge(t1, t2, t3);
            g.addedge(t2, t1, t3);
        }
        printf("%d\n", solve(1, n));
    }
    return 0;
}

原文地址:https://www.cnblogs.com/MyNameIsPc/p/8524589.html

时间: 2024-10-15 11:41:52

poj 1741 Tree(树的点分治)的相关文章

POJ 1741 Tree(树的点分治,入门题)

Tree Time Limit: 1000MS   Memory Limit: 30000K Total Submissions: 21357   Accepted: 7006 Description Give a tree with n vertices,each edge has a length(positive integer less than 1001).Define dist(u,v)=The min distance between node u and v.Give an in

POJ 1741 Tree 树的点分治

题目大意:给定一棵树,求树上距离不超过k的点对(x,y) (x<y)的数量 男人八题第五题...其实没那么难的说...比NOI2014最后一题好写多了0.0 首先两个点之间的路径有两种情况: 1.两点之间路径经过根 2.两点之间路径不经过根 首先讨论情况1 我们从根出发进行一次DFS,求出每个点到根的距离,排序,然后扫一遍数组O(n)出解 但其中如果两个点都属于根的同一棵子树,那么这两个点的路径一定是不经过根的,我们还要把这些点对减掉 于是枚举子树,同样把距离排序,扫数组即可 然后讨论情况2 既

Poj 1741 Tree (树的分治)

题目链接: Poj 1741 Tree 这个题目Tle的好苦啊,原来一直是树的重心没找对,Tle好长时间,终于对了,好感动,先贴个代码. 1 #include <cstdio> 2 #include <cstring> 3 #include <iostream> 4 #include <algorithm> 5 using namespace std; 6 7 const int maxn = 10010; 8 struct node 9 { 10 int

POJ 1741 Tree 树+点分治

树的点分治 可以看09年漆子超论文,说的很详细. Tree Time Limit: 1000MS   Memory Limit: 30000K Total Submissions: 12650   Accepted: 4025 Description Give a tree with n vertices,each edge has a length(positive integer less than 1001). Define dist(u,v)=The min distance betwe

POJ 1741 Tree 树的分治(点分治)

题目大意:给出一颗无根树和每条边的权值,求出树上两个点之间距离<=k的点的对数. 思路:树的点分治.利用递归和求树的重心来解决这类问题.因为满足题意的点对一共只有两种: 1.在以该节点的子树中且不经过该节点. 2.路径经过该节点. 对于第一种点,我们递归处理:第二种点,我们可以将所有子树的节点到这个子树的根节点的距离处理出来,然后排序处理出满足要求的点对的个数. 按照正常的树的结构来分割子树,这样的做法的时间复杂度肯定是不好看的,为了让子树大小尽量相同,我们每次处理这个子树前找到这个子树的重心,

poj 1741(树的点分治)

Tree Give a tree with n vertices,each edge has a length(positive integer less than 1001). Define dist(u,v)=The min distance between node u and v. Give an integer k,for every pair (u,v) of vertices is called valid if and only if dist(u,v) not exceed k

POJ 1741/1987 树的点分治

树的点分治,主要思想是每次找子树的重心,计算经过根节点的情况数,再减去点对属于同一子树的情况. 1 #include <iostream> 2 #include <vector> 3 #include <algorithm> 4 #include <string> 5 #include <string.h> 6 #include <stdio.h> 7 #include <stdlib.h> 8 #include <

POJ 1741 Tree 树分治(点分治)

题意:给你一颗带边权树,问你其中 dis(v,u) <= k 的对数 解题思路: 首先推荐大家看 09年国家集训队漆子超 的论文 看到这题  我们可以有三种思路 第一种是枚举起点,遍历整颗树找对数    时间复杂度 为  O(n^2),空间复杂度为 O(n) 第二种是树形dp的思想     每个节点用 长度为 K 数组维护 ,递归求解  时间复杂度为 O(n ^k)空间复杂度 为 O(n) 第三种就是我们要用到的点分治的思想. 这种思想的具体思路是  先找到一个  根  对这个根进行 深搜, 找

POJ 1741 Tree(树分治)

去网上搜题解大多数都是说论文,搜了论文看了看,写的确实挺好,直接复制过来. 不过代码中有些细节还是要注意的,参考这篇http://blog.sina.com.cn/s/blog_6d5aa19a0100o73m.html一段 设X为满足i<j且Depth[i]+Depth[j]<=K的数对(i,j)的个数设Y为满足i<j,Depth[i]+Depth[j]<=K且Belong[i]=Belong[j]数对(i,j)的个数那么我们要统计的量便等于X-Y 求X.Y的过程均可以转化为以下

POJ 1741 Tree 树形DP(分治)

链接:id=1741">http://poj.org/problem?id=1741 题意:给出一棵树,节点数为N(N<=10000),给出N-1条边的两点和权值,给出数值k,问树上两点最短距离小于k的点对有多少个. 思路:拿到题的第一反应是LCA问题,只是细一想询问次数极限情况能够达到10000*5000次.即使用Tarjan也是超时没商议的. 2009年国家队论文提供了树的分治思想,对于本题就是树的分治的点分治的应用.每次找到能使含节点最多的子树的节点最少的根分而治之,相同方式分