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 integer k,for every pair (u,v) of vertices is called valid if and only if dist(u,v) not exceed k. Write a program that will count how many pairs which are valid for a given tree. Input
The input contains several test cases. The first line of each test case contains two integers n, k. (n<=10000) The following n-1 lines each contains three integers u,v,l, which means there is an edge between node u and v of length l.
The last test case is followed by two zeros. Output
For each test case output the answer on a single line.
Sample Input 5 4 1 2 3 1 3 1 1 4 2 3 5 1 0 0 Sample Output 8 Source |
[Submit] [Go Back] [Status] [Discuss]
分析:
第一次写点分治
树上的路径无非两种:经过根结点的,不经过根结点的
对于不经过根结点的路径,我们可以把ta看做是经过某一棵子树的根结点的路径,换句话说,我们可以递归解决
所以我们现在的问题就是如何计算经过根结点的路径条数:
对于根结点进行一次dfs,求出每个结点的deep(到根结点的距离),并将其从小到大排序
避免重复,只需要求出其中deep[x]≤deep[y]且deep[x]+deep[y]≤m的有序对数(x,y)
计算方式挺简单:
用i表示左指针,j表示右指针,i从左向右遍历
如果deep[i]+deep[j]≤m,则点对(i,t)(i < t ≤ j)都符合题意,将j-i加入答案中,并且i++;否则j - -
然而这样还会重复计算在同一棵子树中的点对,
所以再进行下一步dfs之前需要减去重复部分
然而我们在递归处理子树的时候
每次不能固定选择root,而是以子树重心作为root去处理,这样能保证时间复杂度再O(nlog2n)以下
题目的做法很简单,我们来看一下具体的代码
Frist:找到整棵树的重心
void getroot(int now,int fa)
{
f[now]=0;
sz[now]=1;
for (int i=st[now];i;i=way[i].nxt)
if (way[i].y!=fa&&vis[way[i].y]==0) //vis表示这个连通块没有计算过
{
getroot(way[i].y,now);
sz[now]+=sz[way[i].y];
f[now]=max(f[now],sz[way[i].y]); //结点i连通的最大连通块
}
f[now]=max(f[now],sum-sz[now]);
if (f[now]<f[root]) root=now; //树的重心
}
Second:dfs
我们dfs一下这棵树,但是我按照dfs的顺序计算路径条数
void dfs(int now)
{
deep[now]=0;
vis[now]=1; //以now为根结点的子树(连通块)
ans+=cal(now);
for (int i=st[now];i;i=way[i].nxt)
if (!vis[way[i].y])
{
deep[way[i].y]=way[i].v; //deep
ans-=cal(way[i].y); //去重,把子树内部(不经过根结点)的路径减去
sum=sz[way[i].y]; //新连通块的大小
root=0;
getroot(way[i].y,0); //找到新连通块中的重心
dfs(root);
//为了降低时间复杂度,我们对于每一个连通块都要从重心开始搜索
}
}
其中vis数组是一个很重要的数组,vis[i]代表的就是以i为根结点的子树是否计算过(计算经过根结点i的路径条数)
首先,我们先无脑加上这棵子树内的路径条数
之前就说过我们需要减去没有经过根结点的路径条数
之后就是递归处理的路径条数
但是如果无脑递归,就有可能因为树的形态退化成一条链而导致TLE
所以我们也需要在新的,没有计算过的连通块内找到重心,继续dfs
Third:计算子树内的路径条数
void getdeep(int now,int fa)
{
d[++tt]=deep[now];
for (int i=st[now];i;i=way[i].nxt)
if (way[i].y!=fa&&vis[way[i].y]==0)
{
deep[way[i].y]=deep[now]+way[i].v;
getdeep(way[i].y,now);
}
}
int cal(int now)
{
tt=0;
getdeep(now,0);
sort(d+1,d+1+tt);
int i=1,j=tt,ans=0;
while (i<j)
{
if (d[i]+d[j]<=m) ans+=(j-i),i++;
else j--;
}
return ans;
}
首先我们需要处理出仅仅在这棵子树中,根结点(实际上就是子树中的重心)到各个节点的距离
把各个结点按照从小到大排序,
用i表示左指针,j表示右指针,i从左向右遍历
如果deep[i]+deep[j]≤m,则点对(i,t)(i < t ≤ j)都符合题意,将j-i加入答案中,并且i++;否则j - -
tip
第一次写的点分治,不是很明白
如果距离等于k,cal可以改成:
int cal(int now)
{
tt=0;
getdeep(now,0);
sort(d+1,d+1+tt);
int l=1,r=tt,ans=0;
while (l<r)
{
while (d[l]+d[r]>m&&l<r) r--;
if (r<=l) break;
int rr=r;
while (d[l]+d[r]==m) {
ans++;
r--;
if (r<=l) break;
}
r=rr;
l++;
}
return ans;
}
//这里写代码片
#include<cstdio>
#include<cstring>
#include<iostream>
#include<algorithm>
using namespace std;
const int N=10010;
int n,m;
struct node{
int x,y,v,nxt;
};
node way[N<<1];
int st[N],tot=0,deep[N],f[N],ans,sum,root,sz[N],d[N],tt;
bool vis[N];
void add(int u,int w,int z)
{
tot++;
way[tot].x=u;way[tot].y=w;way[tot].v=z;way[tot].nxt=st[u];st[u]=tot;
}
void getroot(int now,int fa)
{
f[now]=0;
sz[now]=1;
for (int i=st[now];i;i=way[i].nxt)
if (way[i].y!=fa&&vis[way[i].y]==0) //vis表示这个连通块没有计算过
{
getroot(way[i].y,now);
sz[now]+=sz[way[i].y];
f[now]=max(f[now],sz[way[i].y]); //结点i连通的最大连通块
}
f[now]=max(f[now],sum-sz[now]);
if (f[now]<f[root]) root=now; //树的重心
}
void getdeep(int now,int fa)
{
d[++tt]=deep[now];
for (int i=st[now];i;i=way[i].nxt)
if (way[i].y!=fa&&vis[way[i].y]==0)
{
deep[way[i].y]=deep[now]+way[i].v;
getdeep(way[i].y,now);
}
}
int cal(int now)
{
tt=0;
getdeep(now,0);
sort(d+1,d+1+tt);
int i=1,j=tt,ans=0;
while (i<j)
{
if (d[i]+d[j]<=m) ans+=(j-i),i++;
else j--;
}
return ans;
}
void dfs(int now)
{
deep[now]=0;
vis[now]=1; //以now为根结点的子树(连通块)
ans+=cal(now);
for (int i=st[now];i;i=way[i].nxt)
if (!vis[way[i].y])
{
deep[way[i].y]=way[i].v; //deep
ans-=cal(way[i].y); //去重,把子树内部(不经过根结点)的路径减去
sum=sz[way[i].y]; //新连通块的大小
root=0;
getroot(way[i].y,0); //找到新连通块中的重心
dfs(root);
//为了降低时间复杂度,我们对于每一个连通块都要从重心开始搜索
}
}
int main()
{
while (scanf("%d%d",&n,&m)&&n)
{
memset(st,0,sizeof(st)); tot=0;
memset(vis,0,sizeof(vis));
ans=0;
for (int i=1;i<n;i++)
{
int u,w,z;
scanf("%d%d%d",&u,&w,&z);
add(u,w,z); add(w,u,z);
}
f[0]=N,sum=n; //f[i]结点i连通的最大连通块,sum是当前连通块的大小
root=0;
getroot(1,0); //找重心
dfs(root);
printf("%d\n",ans);
}
return 0;
}