点分治 POJ 1741 Tree

来源:互联网 发布:女生舍不得买衣服知乎 编辑:程序博客网 时间:2024/05/19 09:01

题目大意:
扔给你一颗有权无根树,求有多少条路径的长度小于k?

题目要求要对树上的链经行处理,怎么搞呢?其实不难想到点分这个方法。这一道题目本来就是最典型也最基础的点分治题目。
那么怎么点分呢,首先要找到重心。重心就是整棵无根树上所有子树中最大的size最小的点。然后处理树中所有经过这个点的链,重心的性质就决定了它针对一条链这种极端的情况处理的优越性。接着就删掉这个点,再在他的所有子树中重复这种操作直到处理完所有链。
解题步骤:
1.统计经过重心的满足条件的路径条数。
1)统计经过重心,且路径可在重心周围有重合的满足条件的路径条数—O(n)
2)统计经过重心的子节点,且路径可在重心子节点周围有重合的满足路径条数O(n)
3)经过重心满足条件的路径条数=1)-2)中的路径条数(在重心周围有重合的路径其实不会过重心,不在我们的讨论范围之内。在重心周围有重合的路径中包括了(1)在重心周围有重合但是在重心子节点周围无重合的满足条件的路径,和(2)在重心周围有重合并且在重心子节点周围也有重合的满足条件的路径,就等于经过重心子节点的满足条件的路径条数),额…有点绕,多读几遍应该没问题。
2.将重心除去,分为x个子树,分别寻找每个子树的重心,重复上述步骤即可。由于重心的定义可知,递归层数不会超过O(logn) ,因此总体复杂度为O(n*logn)

#include<stdio.h>#include<iostream>#include<algorithm>#include<string.h>#define maxn 20010#define lowbit(x) x&(-x)#define LL long long#define INF 0x3f3f3f3fusing namespace std;int n, k;struct node{    int to,next,w;}ed[maxn*2];int head[maxn], tot;int f[maxn], vis[maxn], siz[maxn], deep[maxn], d[maxn];//getcoreint core, ans, sum, cnt;void init(){    memset(head,0,sizeof(head));    memset(vis,0,sizeof(vis));    tot = 1;    ans = 0;}void add(int a,int b,int c){    ed[tot].to = b;    ed[tot].next = head[a];    ed[tot].w = c;    head[a] = tot++;}void getcore(int u, int fat){//求重心     siz[u] = 1, f[u] = 0;//加上此点     for(int k=head[u]; k; k=ed[k].next){//f数组存,子树大小最大值的最小值         int v = ed[k].to;        if(v == fat || vis[v]) continue;//除去已找到的重心后求子树重心        getcore(v, u);        siz[u] += siz[v];        f[u] = max(f[u], siz[v]);    }    f[u] = max(f[u], sum-siz[u]);//在上面的一个子树(只有一个)     if(f[u] < f[core])        core = u;//core记录该树重心}void getdeep(int u, int fat){//得到u子树每个节点的深度(带权值)    deep[++cnt] = d[u];    for(int k=head[u]; k; k=ed[k].next){        int v = ed[k].to;        int w = ed[k].w;        if(v == fat || vis[v]) continue;        d[v] = d[u] + w;        getdeep(v, u);        //if(u == 1) printf("d[%d]=%d\n", v, d[v]);    }}int cal(int u, int w){//1.该点是重心:求该点子树每点到该点深度2.该点父节点是重心:求该点子树到父节点的深度    cnt = 0;//统计点的个数     d[u] = w;    getdeep(u, 0);    int tmp = 0;    sort(deep+1, deep+cnt+1);//    for(int l=1, r=cnt; l<r;)        if(deep[l] + deep[r] <= k){//因为是顺序查询所以说一个位置满足了那么之后所有的位置就满足了。            tmp += (r - l);            l++;        }        else r--;    /*for(int x=1; x<=cnt; x++){        printf("%d ", deep[x]);    }    printf("\n");*/    //printf("%dans%d\n",u,tmp);    return tmp;}void work(int u){    ans += cal(u, 0);//经过原树重心的k路径条数    //printf("A%dans%d\n",u,ans);    vis[u] = 1;//原树重心已算过,标记    for(int k=head[u]; k; k=ed[k].next){        int v = ed[k].to;        int w = ed[k].w;        if(vis[v]) continue;//如果v已经作为重心枚举过,u不会遍历到v,因此不必除去        ans -= cal(v, w);//除去经过重心同一子树的路径        //printf("B%dans%d\n",u,ans);        sum = siz[v];//更改当前树节点总和        core = 0;//*变为0*        getcore(v, core);        work(core);    }}int main(){    while(scanf("%d%d",&n,&k)!=EOF){        if(!n&&!k) break;        init();        for(int i=0; i<n-1; i++){            int u,v,w;            scanf("%d%d%d",&u,&v,&w);            add(u,v,w);            add(v,u,w);        }        core=0; sum=n; f[0]=INF;//初始sum        getcore(1, 0);//把“1”看做根节点         //printf("core%d\n",core);        work(core);        printf("%d\n",ans);    }    return 0;}
0 0