poj 1741 点分治

点分治就是在树上跑分治。

这个题是求树上两两距离小于K的点对有多少.

可以定义length[i]为i点与根结点的距离。

那么对于其不在同一个根儿子子树的点,这样可以全部计算出来了,现在就差每个子树里的点。也就是说,对于这个根,经过其的点的路径全部算出来了。

此时可以把这个根从图中删掉,那么再递归进行处理后面的根,那么得到的结果即可。

每次都寻找重心来进行分治,这样可以使得复杂度为 nlogn

注意写代码的正确性。。


#include <iostream>
#include <stdio.h>
#include <vector>
#include <string.h>
#include <algorithm>
#include <map>
#include<time.h>
#include<set>
using namespace std;
const int maxn=1e4+7;
struct ttt{
int w,v,next;
}edge[maxn+maxn];
int head[maxn];
int e,n;
bool vis[maxn];
void init(){
    e=0;
    memset(vis,0,sizeof(vis));
    memset(head,-1,sizeof(head));
}
int K;
void add(int u,int v,int w){
    e++;
    edge[e].v=v;
    edge[e].w=w;
    edge[e].next=head[u];
    head[u]=e;
}
int mx[maxn],size[maxn],mi,root,dis[maxn],num;
//mx用来储存每个结点的最大子树大小,size储存每个结点的子树大小,dis存结点
int dfssize(int u,int fa){ //这个函数预处理全部子树的大小和每个点的最大子树大小
    int i,v;
   //cout <<" in in " << u << endl;
    size[u]=1; //初始大小为1,最大子树大小是0
    mx[u]=0;
    for(i=head[u];i!=-1;i=edge[i].next){
        v=edge[i].v;
     //   cout <<"!!!"<<v<< endl;
        if(v != fa && !vis[v]){
            dfssize(v,u);
        size[u]+=size[v];
        mx[u]=max(size[v],mx[u]);
        }
    }
  //  cout <<" OUT OUT  " << u << endl;
}
//dfsroot 的fa只用来判不走回去的边,r用来判从进入到这个点的这个方向的点有多少
int dfsroot(int r,int u,int fa){ //r是指最开始dfs的那个点
    int i,v;
    if(size[r]-size[u]>mx[u])mx[u]=size[r]-size[u]; //这里是考虑从父节点连接下来的那一边
    if(mx[u]<mi)mi=mx[u],root=u;//取最大子树最小的那个点作为根
    for(i=head[u];i!=-1;i=edge[i].next){
        int v=edge[i].v;
        if(vis[v]==1||v==fa)continue;
        dfsroot(r,v,u);
    }
}
int dfsdis(int u,int d,int fa){
    dis[num++]=d; //距离为d
    int i,v;
    for(i=head[u];i!=-1;i=edge[i].next){
        v=edge[i].v;
        if(vis[v]==1||v==fa)continue;
        dfsdis(v,d+edge[i].w,u);
    }
}
int calc(int u,int d){//到这个点的距离已经是d了
    int ret=0;
    num=0;
    dfsdis(u,d,0); //从这个点开始计算出全部点的距离
    sort(dis,dis+num);
    int i=0,j=num-1;
    while(i<j){ //双指针扫
        while(dis[i]+dis[j]>K&&i<j)j--;
        ret+=j-i;i++;
    }
    return ret;
}
int Sum;
int dfs(int u){
  //  cout <<"in"<< u<< endl;
    mi=n;
    dfssize(u,0);
    dfsroot(u,u,0);
    Sum+=calc(root,0); //以root作为根去找
    vis[root]=1;
    int i,v;
    for(i=head[root];i!=-1;i=edge[i].next){
        v=edge[i].v;
        if(!vis[v]){ //每次计算的时候会把同一颗子树上点对也计算了,这样子会重复计算,那么每次剪掉即可
            Sum-=calc(v,edge[i].w);
            dfs(v);
        }
    }
}
int main(){
    int i,j,k,f1,f2,f3,f4,t1,t2,t3,t4,m;
    //freopen("in.txt","r",stdin);
    while(scanf("%d %d",&n,&m)==2){
    if(n+m==0)break;
    K=m;init();
    for(i=1;i<n;i++){
        scanf("%d %d %d",&t1,&t2,&t3);
        add(t1,t2,t3);
        add(t2,t1,t3);
    }
    //for(i=head[1];i!=-1;i=edge[i].next){
      //  cout << edge[i].v <<endl;
    //}
    Sum=0;
    dfs(1);
    printf("%d\n",Sum);
    }
    return 0;
}


  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值