POJ1741 Tree

描述:

给定一颗n个节点的带边权的树(边权不超过1001)

定义dist(u,v)为节点u与节点v之间的最小距离。

给定一整数k,当且仅当dist(u,v)不超过k,那个点对(u,v)就是合法的。

写一程序计算给定树有多少个合法点对。

输入:

输入包含多组测试样例。每组测试样例第一行为整数n,k(n<=10000)。接下来n-1行包含三个整数u,v,l。表示节点u与节点v之间有一条权值为l的边相连接。

输出:

对每组测试样例,输出答案。


分析:

楼教主的男人八题之一,经典树分治。

首先,路径可以根据他所经过的最久远的祖先来分类,以此为依据分在不同类里的路径没有重合。

我们考虑一条路径,他要么经过根节点,要么不经过(但是这种情况他会经过一个子树的根节点)。不经过的情况可以通过dfs的方式考虑。现只考虑经过根节点的情况。

我们一dist[i]表示某点到根节点(也可能是子树的)的距离。那么满足条件的路径的起点和终点应满足dist[i]+dist[j]<=K,同是i和j还不能在同一个孩子节点表示的子树里,否则他会在考虑相应子树的情况时被重复计数。这样每一个节点可以先加上所有的路径(包括重复的),再减去重复的路径。对于重复的路径,只要考虑其孩子节点所表示的子树,减去使之dist[i]+dist[j]<=K的情况(注意这时的dist仍是表示相对于原根节点的距离)。对于这种情况,我们可以将其转化为问题:已知一数列A[1],A[2],A[3]......A[m],求满足A[i]+A[j]<=K的元素对的数目。具体求解过程看代码,可以在O(n)时间内解决。这样对某一节点的路径计数就完成了。接下来只要把所有节点的路径都加起来就是答案了。

另外还要注意一点,当树是一条链时,将要考虑n层,这是时间复杂度退化到O(n^2)。于是我们可以每次找子树的重心(即用该点分割原树,所获得的森林里最大的树节点数最小的节点)。这样可以保证只考虑O(lgn)层,一共的时间复杂度是O(nlg^2n)。

代码:

#include<cstdio>
#include<vector>
#include<cstring>
#include<algorithm>
using namespace std;
#define N 10005
#define max(x,y) ((x)<(y)?(y):(x))
int ans,dist[N],K,n,siz[N],dp[N],Size,root;
/*siz[i]表示以i为根节点的子树中节点的总数,dp[i]表示用节点i分割子树所获取的森林中节点数最大的子树里的节点数*/
/*Size表示所考虑的相应子树里节点的总数,这三个概念在求树的重心时会用到,root表示重心*/
bool done[N];//表示某节点的路径属是否已被考虑,他也是分割原树的标志
struct Node{
    int v,w;
    Node(int v,int w):v(v),w(w){}
};
vector<Node> G[N];
vector<int> d;  //记录子树下所有节点的dist
void getRoot(int u,int fa){  //获取u所在子树的重心,fa为u的父节点,防止死循环
    siz[u]=1;dp[u]=0;int v;  //初始化siz[u]=1,即加上根节点
    for(unsigned int i=0;i<G[u].size();++i){
        if((v=G[u][i].v)==fa||done[v]) continue;   //注意done[v]为真的点不能考虑,实际上就是在这里将不同的子树分割开来的
        getRoot(v,u);siz[u]+=siz[v];dp[u]=max(dp[u],siz[v]);  //向下求解,然后更新siz[u],dp[u]
    }
    dp[u]=max(Size-siz[u],dp[u]);  //考虑u的父节点所在的树,这也是被分割出的森林的一部分
    if(dp[root]>dp[u]) root=u;  //更新root
}
void getDist(int u,int fa){  //获取各节点距根节点距离
    siz[u]=1;int v;d.push_back(dist[u]); //将子树节点的距离加到vector中,最终求解时会用(这实际就是分析中的数组A)
    for(unsigned int i=0;i<G[u].size();++i){
        if((v=G[u][i].v)==fa||done[v]) continue;
        dist[v]=dist[u]+G[u][i].w;
        getDist(v,u);siz[u]+=siz[v];
    }
}
int calc(int key,int init){   //计算以key为根节点的路径数(包括重复的)
    d.clear();dist[key]=init;getDist(key,0);
    sort(d.begin(),d.end());int res=0;
    for(unsigned int i=0,l=d.size()-1;i<l;){
        if(d[i]+d[l]<=K) res+=(int)(l-i++);
        else --l;
    }
    return res;
}
void work(int key){
    ans+=calc(key,0);int v;done[key]=true;  //每次考虑完某个节点以后要标记done数组,也表示对原树的分割
    for(unsigned int i=0;i<G[key].size();++i){
        if(done[v=G[key][i].v]) continue;
        ans-=calc(v,G[key][i].w);  //这里的第二个参数,表示dist的参考点仍是根节点
        dp[0]=Size=siz[v];  //设置初始值
        getRoot(v,root=0);  //找重心
        work(root);  //递归求解
    }
}
int main(){
    int u,v,w;
    while(scanf("%d%d",&n,&K)&&!(K==0&&n==0)){
        memset(done,0,sizeof(done));
        for(int i=1;i<n;++i){
            scanf("%d%d%d",&u,&v,&w);
            G[v].push_back(Node(u,w));
            G[u].push_back(Node(v,w));
        }
        dp[0]=Size=n;getRoot(1,root=0);
        ans=0;work(root);
        printf("%d\n",ans);
        for(int i=1;i<=n;++i) G[i].clear();
    }
    return 0;
}
反思:

树分治+求重心,我也算是八分之一的男人啦哈哈哈哈哈O(∩_∩)O~~

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值