poj1741 Tree (点分治)
题面(具体题目读者可以参考poj1741)
给一棵有 n 个顶点的树,每条边都有一个长度(小于 1001 的正整数)。
定义 dist(u,v)=节点 u 和 v 之间的最小距离。
给定一个整数 k,对于每一对 (u,v) 顶点当且仅当 dist(u,v) 不超过 k 时才称为有效。
编写一个程序,计算给定树有多少对有效。
输入包含几个测试用例。每个测试用例的第一行包含两个整数 n、k。(n<=10000) 下面的 n-1 行每行包含三个整数 u,v,l,这意味着节点 u 和 v 之间存在一条长度为 l 的边。
最后一个测试用例后跟两个零。
sourse:
首先题目给出一棵无根树(没有明确根节点),所以我们选取树的重心作为划分点root。
树上的路径分为两种:1.经过root;2.不经过root(即两点均在root的一颗子树中),我们只需求解第一类路径,第二类路径根据分治策略继续采用重心分解即可得到。
算法设计:
1.求树的重心root;
2.从root出发,统计每个节点到root的距离d[u];
3.维护一个距离数组,对其进行排序,双指针扫描,统计满足条件的数量;
4.对root的每一棵子树v都减去重复统计的节点数;
5.从v出发重复上述过程;
size[i]表示以i为根节点的子树大小;f[i]指i的最大子树的节点数;d[i]指i到根的距离;dep:距离数组
#include#include #include using namespace std; const int maxn=10005; int cnt,n,k,ans,head[maxn]; int root,S,size[maxn],f[maxn],d[maxn],dep[maxn]; bool vis[maxn]; struct edge{ int to,next,w; }edge[maxn*2]; void add(int u,int v,int w) { edge[++cnt].to=v; edge[cnt].w=w; edge[cnt].next=head[u]; head[u]=cnt; } void getroot(int u,int fa)//获取重心 { size[u]=1; f[u]=0;//删除u后,最大子树的大小 for(int i=head[u];i;i=edge[i].next) { int v=edge[i].to; if(v!=fa&&!vis[v]) { getroot(v,u); size[u]+=size[v]; f[u]=max(f[u],size[v]); }
} f[u]=max(f[u],S-size[u]);//S为当前子树总结点数 if(f[u]} return sum; } void solve(int u) //获取答案 { vis[u]=true; ans+=getsum(u,0); for(int i=head[u];i;i=edge[i].next) { int v=edge[i].to; if(!vis[v]) { ans-=getsum(v,edge[i].w);//减去重复 root=0; S=size[v]; getroot(v,u); solve(root); } } } int main() { f[0]=0x7fffffff;//初始化树根 while(scanf("%d%d",&n,&k),n+k) { memset(vis,0,sizeof(vis)); memset(head,0,sizeof(head)); cnt=0;ans=0; for(int i=1;i<=n-1;i++) { int x,y,z; scanf("%d%d%d",&x,&y,&z); add(x,y,z); add(y,x,z); } root=0; S=n; getroot(1,0); solve(root); printf("%d\n",ans); } return 0; }