您的位置:首页 > 其它

POJ1741 Tree

2016-01-24 20:34 267 查看
描述:

给定一颗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
,K,n,siz
,dp
,Size,root;
/*siz[i]表示以i为根节点的子树中节点的总数,dp[i]表示用节点i分割子树所获取的森林中节点数最大的子树里的节点数*/
/*Size表示所考虑的相应子树里节点的总数,这三个概念在求树的重心时会用到,root表示重心*/
bool done
;//表示某节点的路径属是否已被考虑,他也是分割原树的标志
struct Node{
int v,w;
Node(int v,int w):v(v),w(w){}
};
vector<Node> G
;
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~~
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签:  poj 树分治 男人八题