poj 1741 Tree 树的分治

这个题自己完全不会做,,,只是看了解题报告之后自己尝试写了一遍。发现思路很是清晰


首先是涉及到了树怎样进行分治,然后就引入了重心的概念,所谓树的重心就是删除这个点之后所得到的所有子树的最大顶点数最小,这样的话不管怎么切割这颗树,复杂度都不会退化得太严重


然后具体算法就是把这颗树以重心为顶点分为几颗子树,那么满足条件的点对,要么是在同一子树中,要么是在不同两颗子树中,要么其中一个点是重心,就这样递归处理这颗树就能得到答案了,不过在统计的时候有可能出现重复统计的时候,应当删除掉。


#include<iostream>
#include<sstream>
#include<cstdio>
#include<cstring>
#include<cmath>
#include<queue>
#include<stack>
#include<math.h>
#include<map>
#include<time.h>
#include<set>
#include<string>
#include<vector>
#include<algorithm>
using namespace std;
#define inf 0x7fffffff
#define lc l,m,index<<1
#define rc m+1,r,index<<1|1
#define max_n 200005
#define mod 10000007
#define LL  long long
int n,k;
struct edge
{
    int to,length;
};
int ans;
vector<edge>G[max_n];
int num[max_n]; //统计子树节点个数
bool use[max_n]; //记录节点是否被当做重心
void init()
{
    for(int i=0;i<=10005;i++)
    G[i].clear();
    memset(num,0,sizeof(num));
    memset(use,false,sizeof(use));
}

int cntnum(int v,int p)   //返回子树节点个数
{
    int c=1;
    for(int i=0;i<G[v].size();i++)
    {
        int w=G[v][i].to;
        if(w!=p && !use[w])
        {
            c+=cntnum(w,v);
        }
    }
    num[v]=c;
    return c;
}
pair<int ,int > findcen(int v,int p,int t) //寻找重心,返回值为最大顶点数和顶点编号
{
    int s=1,m=0;
    pair<int,int> res = make_pair(inf,-1);
    for(int i=0;i<G[v].size();i++)
    {
        int w=G[v][i].to;
        if(w==p || use[w])continue;
        res=min(res,findcen(w,v,t));
        s+=num[w];
        m=max(m,num[w]);
    }
    m=max(t-s,m);
    res=min(res,make_pair(m,v));
    return res;
}
void findlen(int v,int p,int d,vector<int> &ds)  //记录其他点距离中心点的距离
{
    ds.push_back(d);
    for(int i=0;i<G[v].size();i++)
    {
        int w=G[v][i].to;
        if(w==p || use[w])continue;
        findlen(w,v,d+G[v][i].length,ds);
    }
}
int judge(vector<int> &ds)
{
    int res=0;
    sort(ds.begin(),ds.end());
    int j=ds.size();
    for(int i=0;i<ds.size();i++)
    {
        while(j>0 && ds[i]+ds[j-1]>k)j--;
        res+=j-(j>i?1:0);
    }
  //  printf(" res ===  %d\n",res);
    return res/2;
}
void solve_problem(int v)    //solve
{
    cntnum(v,-1);
    int s=findcen(v,-1,num[v]).second;
    use[s]=true;


    //1
    for(int i=0;i<G[s].size();i++)
    {
        if(use[G[s][i].to]) continue;
        solve_problem(G[s][i].to);
    }

    //2,3;
    vector<int> d;
    d.push_back(0);
    for(int i=0;i<G[s].size();i++)
    {
        int w=G[s][i].to;
        if(use[w])continue;
        vector<int>ds;
        findlen(w,s,G[s][i].length,ds);
        ans-=judge(ds);
      //  printf("ans  == %d \n",ans);
        d.insert(d.end(),ds.begin(),ds.end());
    }
    ans+=judge(d);

   // printf("ans  ==  %d\n",ans);
    use[s]=false;
}
void solve()
{
    ans=0;
    solve_problem(1);
    printf("%d\n",ans);
}
int main()
{
    while(~scanf("%d%d",&n,&k) && !(n==0 && k==0))
    {
        init();
        for(int i=0;i<n-1;i++)
        {
            int a,b,l;
            scanf("%d%d%d",&a,&b,&l);
            edge e;
            e.length=l;
            e.to=b;
            G[a].push_back(e);
            e.to=a;
            G[b].push_back(e);
        }
        solve();
    }
    return 0;
}


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值