Tree
原题链接
Time Limit: 1000MS Memory Limit: 30000K
Total Submissions: 26564 Accepted: 8849
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
LouTiancheng@POJ
树
原题链接
时间限制:1000MS内存限制:30000K
提交总数:26564通过人数:8849
描述
给一棵有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的边。
最后的测试用例之后是两个零。
输出
对于每个测试用例,只需一行输出答案。
示例输入
5 4
1 2 3
1 3 1
1 4 2
3 5 1
0 0
示例输出
8
来源
LouTiancheng@ POJ
题解
题目大意不解释。
对于无根树的题目,我们往往会想把它弄成有根树(会好处理一点)。一棵树上我们争取一次性求出所有符合的点对。定义 dis[i] 等于,当前树根到 i 的距离,如果 dis[a] + dis[b] ≤ K,那么这对点肯定符合条件。
对 dis 数组排序,用两个指针 i,j 指向 dis[1] 和 dis[last],如果 dis[i] + dis[j] > K,那么 dis[i ~ j-1] + dis[j] > K,所以我们需要把 j 往前推(能与dis[last] 配对的点求完了)。如果 dis[i] + dis[j] ≤ K,那么 dis[i ~ j-1] + dis[j] ≤ K,所以 ans+= j - i 。
j - i = (j - 1) - i + 1
接下来,再对每棵子树重复操作。但是这样可能使得有的点对被重复计算。
只要 k 大于 3,那么下图中 (3,4) 点对就被计算了两次。
所以要减去每棵子树下(假设子树的根为 son[i],上一树根到 son[i] 的距离为 w[i]),所有 dis[a] + dis[b] + w[i]×2 ≤ K 的点对都是之前重复计算的点对。
到了这里,还有一个问题。如果你遇到的树刚好是一个树链,那么,复杂度就会退化为 N2 N 2 (实际上更大)。我们希望可以把根定在树链的中心,这样可以防止复杂度退化。所以每次的树根都要取子树的重心。
代码
#include<cstdio>
#include<cstring>
#include<iostream>
#include<algorithm>
#define LL long long
using namespace std;
const int maxn=10005;
int n,K,tot,ans,lnk[maxn],son[maxn<<1],nxt[maxn<<1],w[maxn<<1],sum[maxn],f[maxn],dis[maxn],rot,s[maxn];
bool vis[maxn];
//sum表示当前节点为根的子树的节点个数(假设一开始就是有根树);f表示当前节点的 。用于求树的重心
//s用来储存当前这棵子树上的分治操作时,子树上每个节点到根的距离
int read()
{
int ret=0,f=1;char ch=getchar();
while (ch<'0'||ch>'9') {if (ch=='-') f=-1;ch=getchar();}
while (ch>='0'&&ch<='9') ret=ret*10+ch-'0',ch=getchar();
return ret*f;
}
void add(int x,int y,int s){son[++tot]=y;nxt[tot]=lnk[x];lnk[x]=tot;w[tot]=s;}
void getrot(int x,int lst)//找重心
{
sum[x]=1;f[x]=0;
for (int i=lnk[x];i;i=nxt[i])
{
if (son[i]==lst||vis[son[i]]) continue;
getrot(son[i],x);
sum[x]+=sum[son[i]];
f[x]=max(f[x],sum[son[i]]);
}
f[x]=max(f[x],n-sum[x]);
//由于我们把树变成了有根树,我们统计的是每个节点下方的节点个数,实际上上方的节点可能更多
//否则每次都取叶节点就好了?
if (f[x]<f[rot]||rot==0) rot=x;
}
void dfs(int x,int lst)
{
for (int i=lnk[x];i;i=nxt[i])
{
if (son[i]==lst||vis[son[i]]) continue;
s[++tot]=dis[son[i]]=dis[x]+w[i];
dfs(son[i],x);
}
}
int getans(int x,int v)
{
s[tot=1]=dis[x]=v;
dfs(x,0);int sum=0;
sort(s+1,s+tot+1);
for (int i=1,j=tot;i<j;) if (s[i]+s[j]<=K) sum+=j-i,i++;else j--;
return sum;
}
void work(int x)
{
ans+=getans(x,0);vis[x]=1;
for (int i=lnk[x];i;i=nxt[i])
{
if (vis[son[i]]) continue;
ans-=getans(son[i],w[i]);
rot=0;n=sum[son[i]];
getrot(son[i],0);
work(rot);
}
}
int main()
{
while (1)
{
n=read(),K=read();if (!n&&!K) break;
memset(lnk,0,sizeof lnk);tot=0;
memset(vis,0,sizeof vis);
for (int i=1;i<n;i++)
{
int x=read(),y=read(),s=read();
add(x,y,s);add(y,x,s);
}
rot=0;getrot(1,0);
ans=0;work(rot);
printf("%d\n",ans);
}
return 0;
}