1. 程式人生 > >poj 1741 Tree (樹的分治)

poj 1741 Tree (樹的分治)

Tree
Time Limit: 1000MS   Memory Limit: 30000K
Total Submissions: 30928   Accepted: 10351

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

 

題目大意:

給定一棵n元樹,求有多少點對使得這兩點的距離小於等於k。

 

樹分治經典題。

以下刪改自sdj222555的CSDN部落格。

 

需要分治。可以看09年漆子超的論文。本題用到的是關於點的分治。

一個重要的問題是,為了防止退化,所以每次都要找到樹的重心然後分治下去,所謂重心,就是刪掉此結點後,剩下的結點最多的樹結點個數最小。

每次分治,我們首先算出重心,為了計算重心,需要進行兩次dfs,第一次把以每個結點為根的子樹大小求出來,第二次是從這些結點中找重心。

當然在一次dfs過程中也能做到。另外雖然說不記憶化也能做到,但還是用個son陣列記憶下吧。

另外,無向圖的dfs不需要像我之前一樣遇一個點打個vis標記,dfs定義成dfs(u,pa),儲存該點的父親就不會返回去啦。

找到重心後,需要統計所有結點到重心的距離,看其中有多少對小於等於K。

這裡採用的方法就是把所有的距離存在一個數組裡(不要忘了重心到自己的),進行快速排序,這是nlogn的,然後用一個經典的相向搜尋O(n)時間內解決。

但是這些求出來滿足小於等於K的裡面只有那些路徑經過重心的點對才是有效的,也就是說在同一顆子樹上的肯定不算數的。所以對每顆子樹,把子樹內部的滿足條件的點對減去。

最後的複雜度是n logn logn    其中每次快排是nlogn 而遞迴的深度為logn。

程式碼實現比較複雜,今後有時間還是可以再敲一次。

 

#include <cstdio>
#include <iostream>
#include <algorithm>
#include <cstring>
#include <string>
#include <cmath>
#include <queue>
#include <deque>
#include <stack>
#include <map>
#include <set>
typedef long long ll;
const int maxn=10000;
const int inf=1000000000;

int n,k;

int to[maxn*2+5];
int w[maxn*2+5];
int next[maxn*2+5];
int head[maxn+5];
int cnt;

void addedge(int a,int b,int c)
{
    to[cnt]=b;w[cnt]=c;
    next[cnt]=head[a];head[a]=cnt++;
}

int ans;
int mins,root;
int son[maxn+5];
int vis[maxn+5];
int depth[maxn+5];
int dis[maxn+5],tot;

void getroot(int u,int pa,int num)//求重心
{
    int maxs=0;
    son[u]=1;
    for(int i=head[u];i!=-1;i=next[i])
    {
        int l=to[i];
        if(l!=pa&&!vis[l])
        {
            getroot(l,u,num);
            maxs=std::max(maxs,son[l]);
            son[u]+=son[l];
        }
    }
    maxs=std::max(maxs,num-son[u]);
    if(maxs<mins)
    {
        mins=maxs;
        root=u;
    }
}

void getdepth(int u,int pa)//通過深度得出需要的dis值
{
    for(int i=head[u];i!=-1;i=next[i])
    {
        int l=to[i];
        if(l!=pa&&!vis[l])
        {
            depth[l]=depth[u]+w[i];
            dis[tot++]=depth[l];
            getdepth(l,u);
        }
    }
}

int calc(int u,int pa,int d)
{
    depth[u]=0;
    tot=0;
    dis[tot++]=0;
    getdepth(u,pa);
    int ret=0;
    std::sort(dis,dis+tot);
    int i=0,j=tot-1;
    while(i<j)//經典的相向搜尋
    {
        while(dis[i]+dis[j]+d*2>k&&i<j)
            j--;
        ret+=j-i;
        i++;
    }
    return ret;
}

void dfs(int x,int num)
{
    mins=inf;
    getroot(x,-1,num);//找重心
    ans+=calc(root,-1,0);//計算整棵樹符合條件的對數
    for(int i=head[root];i!=-1;i=next[i])
    {
        int l=to[i];
        if(!vis[l])
            ans-=calc(l,root,w[i]);//減去每棵子樹符合條件的對數
    }
    vis[root]=1;//打標記
    for(int i=head[root];i!=-1;i=next[i])
    {
        int l=to[i];
        if(!vis[l])
            dfs(l,son[l]);//向子樹遞迴
    }
}

int main()
{
    while(scanf("%d%d",&n,&k),n||k)
    {
        memset(head,-1,sizeof(head));
        cnt=0;
        for(int i=1,a,b,c;i<=n-1;i++)
        {
            scanf("%d%d%d",&a,&b,&c);
            addedge(a,b,c);
            addedge(b,a,c);
        }

        memset(vis,0,sizeof(vis));
        ans=0;
        dfs(1,n);

        printf("%d\n",ans);
    }
    return 0;
}
View Code