POJ1741 tree (点分治模板)

题目大意:

给一棵有 n 个顶点的树,每条边都有一个长度(小于 1001 的正整数)。
定义 dist(u,v)=节点 u 和 v 之间的最小距离。
给定一个整数 k,对于每一对 (u,v) 顶点当且仅当 dist(u,v) 不超过 k 时才称为有效。
编写一个程序,计算给定树有多少对有效。

算法分析:

这道题是一道标准的点分治模板题。题目给定的树是一颗无根树,我们首先选取点作为根节点,这里选取树的重心root作为划分点,使得将树划分得尽量均衡。然后我们统计每个点到根节点的距离,将这些距离加入到一个距离数组中,排序,用双指针扫描,就可以统计以root为根的满足条件的节点数,这里还要用到容斥的思想,我们对root的子树v都要减去重复统计的节点数,从v出发重复以上的过程。

要算两个节点之间的最小距离不超过k,以root为根,则有两种情况:

1.两个点在以root为根的同一颗子树中;

2.两个点在不同子树中;

  1 #include<cstdio>
  2 #include<cstring>
  3 #include<algorithm>
  4 using namespace std;
  5 const int maxn=10005;
  6 int cnt,n,k,ans,head[maxn];
  7 int root,S,size[maxn],f[maxn],d[maxn],dep[maxn];
  8 bool vis[maxn];
  9 struct edge
 10 {
 11     int to,next,w;
 12 }edge[maxn*2];
 13  
 14 void add(int u,int v,int w)
 15 {
 16     edge[++cnt].to=v;
 17     edge[cnt].w=w;
 18     edge[cnt].next=head[u];
 19     head[u]=cnt;
 20 }
 21  
 22 void getroot(int u,int fa)//获取重心
 23 {
 24     size[u]=1;
 25     f[u]=0;//删除u后,最大子树的大小 
 26     for(int i=head[u];i;i=edge[i].next)
 27     {
 28         int v=edge[i].to;
 29         if(v!=fa&&!vis[v])
 30         {
 31             getroot(v,u);
 32             size[u]+=size[v];
 33                f[u]=max(f[u],size[v]);
 34         }
 35     }    
 36     f[u]=max(f[u],S-size[u]);//S为当前子树总结点数 
 37     if(f[u]<f[root])
 38         root=u;
 39 }
 40  
 41 void getdep(int u,int fa)//获取距离
 42 {
 43     dep[++dep[0]]=d[u];//保存距离数组 
 44     for(int i=head[u];i;i=edge[i].next)
 45     {
 46         int v=edge[i].to;
 47         if(v!=fa&&!vis[v])
 48         {
 49             d[v]=d[u]+edge[i].w;
 50             getdep(v,u);
 51         }
 52     }
 53 }
 54  
 55 int getsum(int u,int dis) //获取u的子树中满足个数
 56 {
 57     d[u]=dis;
 58     dep[0]=0;
 59     getdep(u,0);
 60     sort(dep+1,dep+1+dep[0]);
 61     int L=1,R=dep[0],sum=0;
 62     while(L<R)
 63         if(dep[L]+dep[R]<=k)
 64             sum+=R-L,L++;
 65         else
 66             R--;
 67     return sum;
 68 }
 69  
 70 void solve(int u) //获取答案
 71 {
 72     vis[u]=true;
 73     ans+=getsum(u,0);
 74     for(int i=head[u];i;i=edge[i].next)
 75     {
 76         int v=edge[i].to;
 77         if(!vis[v])
 78         {
 79             ans-=getsum(v,edge[i].w);//减去重复
 80             root=0;
 81             S=size[v];
 82             getroot(v,u);
 83             solve(root);
 84         }
 85     }
 86 }
 87  
 88 int main()
 89 {
 90     f[0]=0x7fffffff;//初始化树根 
 91     while(scanf("%d%d",&n,&k),n+k)
 92     {
 93         memset(vis,0,sizeof(vis));
 94         memset(head,0,sizeof(head));
 95         cnt=0;ans=0;
 96         for(int i=1;i<=n-1;i++)
 97         {
 98             int x,y,z;
 99             scanf("%d%d%d",&x,&y,&z);
100             add(x,y,z);
101             add(y,x,z);
102         }
103         root=0;
104         S=n;
105         getroot(1,0);
106         solve(root);
107         printf("%d\n",ans);
108     }
109     return 0;
110 }

每次选取重心作为划分点,点分治最多递归O(logn)层,距离数组排序O(nlogn),总的时间复杂度O(nlog^{2}n)。

 

posted @ 2022-04-04 14:28  YHXo  阅读(29)  评论(0编辑  收藏  举报