POJ-1741 Tree

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;
}
  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值