POJ 1741tree-点分治入门

学习了一下点分治,如果理解有误还请不吝赐教。

为了快速求得树上任意两点之间距离满足某种关系的点对数,我们需要用到这种算法。

点分治是树上的一种分治算法,依靠树和子树之间的关系进行分治从而降低复杂度。

和其他树上的算法有一些区别的是,点分治算法不是先处理局部信息,再将他们汇总得到整个树的信息。点分治处理的问题一般不是树整体的信息,而是树上局部的关系,这就导致我们不能将它看作一个整体,而应该从一开始就处理,在从上往下处理的过程中不断完善信息。在这种思想下我觉得能够更好的理解这个算法。

例如:

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

题目的大概意思就是说,想要找到树上两点之间距离小于k的点对数(路径是有长度的)。我们如何使用点分治算法来解决这个问题呢?

为了找到合适的分治方法,我们引入一个概念叫做树的重心。树的重心指的是以该节点为根形成的最大子树规模最小的节点。听起来可能有点绕,其实还是挺符合直觉的。指的就是,一个树有多个节点,每个节点都可以作为这个树的根,而一旦根节点确定,就会有多个子树,这些子树中规模最大的一般是根节点下直接相连的某个子树。所谓树的重心,就是这个最大子树规模最小的节点。比如:在这里插入图片描述
在上面这个图中,我们选择i节点作为树的重心,它的子树中规模最大的是3,选择其他的都会比3大。

知道什么是重心以后,我们来看如何求一个树的重心。直接看代码吧

void GetRoot(int x,int father)
{int v;SonNum[x]=1;//子树节点的总个数MaxNum[x]=1;//最大子树的节点个数for(int i=Last[x];i;i=Edge[i].last){v=Edge[i].to; //Vis[v]是用来标记是否已经处理过该节点了,这个标记会在后面修改,如果已经处理过我们就不要将这个节点再考虑进来了if(v==father || Vis[v]) continue;GetRoot(v,x);SonNum[x]+=SonNum[v];if(SonNum[v]>MaxNum[x]) MaxNum[x]=SonNum[x];}MaxNum[x]=max(MaxNum[x],ss-SonNum[x]);if(rootx>MaxNum[x]) root=x,rootx=MaxNum[x];
}

得到树的重心以后我们就要对他进行处理,首先当然是统计子树上任意一点到树根(也就是树的重心)的距离

void GetDis(int x,int father,int dis)
{int v;//Dis数组记录树上其他点到重心的距离,因为我们不需要知道哪个点,所以直接保存就可以Dis[++dlen]=dis;for(int i=Last[x];i;i=Edge[i].last){v=Edge[i].to; if(v==father|| Vis[v]) continue;GetDis(v,x,dis+Edge[i].len);}
}

得到距离以后我们就可以根据题目的要求进行计数啦。这里要求的是任意两点的距离小于k,那我们就要先得到两点间的距离。在以重心为根的树上,在两个不同子树上的点的距离就是他们距离重心的距离和。可是如果在同一个子树上的话就不是这样了。但是我们不好确定两个节点是否在同一个子树上,所以先囫囵吞枣将同一个子树上的都计算上,然后再访问子树将他们减去。

int Count(int x,int dis)
{for(int i=0;i<=dlen;++i) Dis[i]=0;dlen=0;GetDis(x,0,dis);sort(Dis+1,Dis+1+dlen);int l=1,r=dlen,ret=0;//如果l到r的距离小于k,则l到l和r之间的任意一点的距离都小于k,所以直接加上r-lwhile(l<=r){if(Dis[l]+Dis[r]<=kk) ret+=r-l,l++;else r--;}return ret;
}

但是显然这样是多算的,我们要减去在同一个子树上的满足条件的节点,他们会在更后面再次加上。

void Solve(int x)
{int v;ans+=Count(x,0);Vis[x]=true;for(int i=Last[x];i;i=Edge[i].last){v=Edge[i].to; if(Vis[v]) continue;//在这里减去同一个子树的上错误加上的点ans-=Count(v,Edge[i].len);ss=SonNum[v]; rootx=INT_MAX; root=0;//处理子树,再加上正确的点GetRoot(v,x);Solve(root);}
}

可能稍微有些难以理解的是为什么这样就可以减去刚开始错误地加上的同一个子树上的点。这里稍微解释一下:

在这里插入图片描述

还是以这个图为例,假如我们一开始处理的是树的重心i节点,那么我们正确计算的就是分布在四个子树上之间的距离,错误计算的就是同一个子树之间的距离,例如:D-A-i-A-EE-A-i-A等,为了处理这个问题,我们后面又访问了一下子节点,注意上面的Solve函数中的ans-=Count(v,Edge[i].len);,为什么这样写就可以减去子节点的影响呢?需要注意我们已经将重心的Vis[x]的值已经修改,因此子节点无法访问除了当前子树外的其他子树,而且有一个初值Edge[i].len,这样处理以后他们的Dis数组的值和之前从重心访问是相同的。也就是说,之前会计入答案的,这里也会再次记入答案,且只计入了同一个子树中的。减去他们以后就是正确的数目。

然后我们再访问子树。将子树看作一个单独的树,再次同样的处理。

AC代码:

#include<iostream>
#include<cstring>
#include<cstdio>
#include<climits>
#include<algorithm>
#include<ctime>
#include<cstdlib>
#include<queue>
#include<set>
#include<map>
#include<cmath>using namespace std;const int MAXN=1e5+5;
struct edge
{int to,len,last;
}Edge[MAXN<<1]; int Last[MAXN],tot;
int n,kk,SonNum[MAXN],MaxNum[MAXN],Vis[MAXN],Dis[MAXN];
int ans,root,rootx,dlen,ss;int getint()
{int x=0,sign=1; char c=getchar();while(c<'0' || c>'9'){if(c=='-') sign=-1; c=getchar();}while(c>='0' && c<='9'){x=x*10+c-'0'; c=getchar();}return x*sign;
}void Init()
{for(int i=0;i<=n;++i) Last[i]=0; tot=0; ans=0; for(int i=0;i<=n;++i) Vis[i]=false;
}void AddEdge(int u,int v,int w)
{Edge[++tot].to=v; Edge[tot].len=w; Edge[tot].last=Last[u]; Last[u]=tot;
}void Read()
{int u,v,w;for(int i=1;i<n;i++){u=getint(); v=getint(); w=getint();AddEdge(u,v,w); AddEdge(v,u,w);}
}void GetRoot(int x,int father)
{int v;SonNum[x]=1; MaxNum[x]=1;for(int i=Last[x];i;i=Edge[i].last){v=Edge[i].to; if(v==father || Vis[v]) continue;GetRoot(v,x);SonNum[x]+=SonNum[v];if(SonNum[v]>MaxNum[x]) MaxNum[x]=SonNum[x];}MaxNum[x]=max(MaxNum[x],ss-SonNum[x]);if(rootx>MaxNum[x]) root=x,rootx=MaxNum[x];
}void GetDis(int x,int father,int dis)
{int v;Dis[++dlen]=dis;for(int i=Last[x];i;i=Edge[i].last){v=Edge[i].to; if(v==father|| Vis[v]) continue;GetDis(v,x,dis+Edge[i].len);}
}int Count(int x,int dis)
{for(int i=0;i<=dlen;++i) Dis[i]=0;dlen=0;GetDis(x,0,dis);sort(Dis+1,Dis+1+dlen);int l=1,r=dlen,ret=0;while(l<=r){if(Dis[l]+Dis[r]<=kk) ret+=r-l,l++;else r--;}return ret;
}void Solve(int x)
{int v;ans+=Count(x,0);Vis[x]=true;for(int i=Last[x];i;i=Edge[i].last){v=Edge[i].to; if(Vis[v]) continue;ans-=Count(v,Edge[i].len);ss=SonNum[v]; rootx=INT_MAX; root=0;GetRoot(v,x);Solve(root);}
}void Work()
{rootx=INT_MAX; ss=n; root=0;GetRoot(1,0); Solve(root);
}void Write()
{printf("%d\n",ans); 
}int main()
{while(1){Init();n=getint(); kk=getint();if(n==0 && kk==0) break;Read();Work();Write();}return 0;
}

参考博客:传送门

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/383756.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

基于单链表的生产者消费者问题

『生产者与消费者问题分析』「原理」生产者生产产品&#xff0c;消费者消费产品。产品如果被消费者消费完了&#xff0c;同时生产者又没有生产出产品&#xff0c;消费者 就必须等待。同样的&#xff0c;如果生产者生产了产品&#xff0c;而消费者没有去消费&#x…

C++智能指针(二)模拟实现三种智能指针

https://blog.csdn.net/nou_camp/article/details/70186721在上一篇博客中提到了Auto_ptr(C智能指针&#xff08;一&#xff09;)&#xff0c;下面进行模拟实现Auto_ptr 采用类模板实现 #include<iostream> using namespace std; template<class T> class Autoptr …

C++智能指针(三)总结

https://blog.csdn.net/nou_camp/article/details/70195795 在上一篇博客中&#xff08;C智能指针&#xff08;二&#xff09;&#xff09;模拟实现了三种智能指针。 其中最好的就是shared_ptr,但是这并不代表它就是最完美的&#xff0c;它也有问题&#xff0c;这个问题就是循环…

c++11 你需要知道这些就够了

https://blog.csdn.net/tangliguantou/article/details/50549751c11新特性举着火把寻找电灯今天我就权当抛砖引玉&#xff0c;如有不解大家一起探讨。有部分内容是引用自互联网上的内容&#xff0c;如有问题请联系我。T&& 右值引用 std::move 右值引用出现之前我们只能…

c++仿函数 functor

https://www.cnblogs.com/decade-dnbc66/p/5347088.html内容整理自国外C教材先考虑一个简单的例子&#xff1a;假设有一个vector<string>&#xff0c;你的任务是统计长度小于5的string的个数&#xff0c;如果使用count_if函数的话&#xff0c;你的代码可能长成这样&#…

Ubuntu软件更新失败

刚安装好Ubuntu以后需要将系统的软件都更新一下&#xff0c;但是遇到一个问题就是下载仓库信息失败&#xff0c;大概是这个样子的错误&#xff1a; 经国遇到这样的问题可以试一下下面这个命令&#xff1a; sudo rm -rf /var/lib/apt/lists/* sudo apt-get update参考网址&…

getsockname函数与getpeername函数的使用

https://www.tuicool.com/articles/V3Aveygetsockname和getpeername函数 getsockname函数用于获取与某个套接字关联的本地协议地址 getpeername函数用于获取与某个套接字关联的外地协议地址 定义如下&#xff1a;[cpp] view plaincopy#include<sys/socket.h> int gets…

Linux命令【一】基本命令

shell命令和bash命令相同&#xff0c;指的是命令解析器 快捷键 history 所有的历史命令ctrl P 向上滚动命令 ctrl N 向下滚动命令 ctrlB将光标向前移动 ctrlF将光标向后移动 ctrlA移动到命令行头部 ctrlE移动到命令行尾部 光标删除操作&#xff1a;删除光标前面字符ctrlh或…

剑指offer面试题:替换空格

https://blog.csdn.net/yanxiaolx/article/details/52235212题目&#xff1a;请实现一个函数&#xff0c;把字符串中的每个空格替换成“%20”。例如输入“We are happy.”&#xff0c;则输出“We%20are%20happy.”。解析&#xff1a;时间复杂度为O(n)的解法。完整代码及测试用例…

数据库原理及应用【一】引言

什么是数据库&#xff1a;一个大规模的集成的数据集合 作用&#xff1a;描述现实世界的实体(entities)以及实体之间的关系 管理数据库的系统软件&#xff1a;DBMS 文件是一个平滑的字符流&#xff0c;无法完成信息的检索和管理 数据&#xff08;data&#xff09;:用来描述现…

用c++模拟实现一个学生成绩管理系统

https://blog.csdn.net/yanxiaolx/article/details/53393437题目&#xff1a;用c模拟实现一个学生成绩的信息管理系统&#xff0c;要求能添加、删除、修改、查看和保存学生的信息等功能 源代码如下:[cpp] view plaincopy#define _CRT_SECURE_NO_WARNINGS #include<iostr…

Python3列表

操作&#xff1a;索引、切片、加、乘、检查成员、确定序列长度、确定最大最小元素 定义&#xff1a; 列表名 [元素]下标列表名[x] 截取:列表名[x:y] 更新&#xff1a; list[x]y 或者使用append()方法添加列表项删除&#xff1a; del list[x]常用操作&#xff1a; 截取与…

Linux惊群效应详解(最详细的了吧)

https://blog.csdn.net/lyztyycode/article/details/78648798?locationNum6&fps1 linux惊群效应详细的介绍什么是惊群&#xff0c;惊群在线程和进程中的具体表现&#xff0c;惊群的系统消耗和惊群的处理方法。1、惊群效应是什么&#xff1f;惊群效应也有人叫做雷鸣群体效应…

epoll原理详解(最清晰)

https://blog.csdn.net/lyztyycode/article/details/79491419我只是把内容搬运过来做个记录&#xff0c;方便自己以后回头看。第一部分&#xff1a;select和epoll的任务关键词&#xff1a;应用程序 文件句柄 用户态 内核态 监控者要比较epoll相比较select高效在什么地方&#x…

Ubuntu卸载软件

用过使用dpkg软件管理工具得到所有已经安装的软件&#xff0c;如果不清楚软件的全名可以使用grep命令进行查找 然后再使用sudo apt-get remove --purge 软件名卸载软件&#xff08;--purge参数会删除配置文件&#xff0c;删的干净一些&#xff09; 例如&#xff1a;

一个重要且实用的signal---SIGCHLD

https://blog.csdn.net/lyztyycode/article/details/78150805SIGCHLD(修改)因为笔者之前的文章里面有错误&#xff0c;今天发现&#xff0c;立马做个修改。在下面我的一段关于sigchld信号相对于直接调用wait函数的好处时&#xff0c;我说调用wait函数要一直检测子进程是否执行完…

Python3函数和代码复用

函数的定义 def 函数名([参数列表]):注释函数体注意事项 函数形参不需要声明类型&#xff0c;可以使用return语句在结束函数执行的同时返回任意类型的值&#xff0c;函数返回值类型与return语句返回表达式i的类型一致 即使该函数不需要接受任何参数&#xff0c;也必须保留一堆…

一文说尽C++赋值运算符重载函数(operator=)

http://www.cnblogs.com/zpcdbky/p/5027481.html在前面&#xff1a;关于C的赋值运算符重载函数(operator)&#xff0c;网络以及各种教材上都有很多介绍&#xff0c;但可惜的是&#xff0c;内容大多雷同且不全面。面对这一局面&#xff0c;在下在整合各种资源及融入个人理解的基…

Python a和a[:]的区别

简单来讲a[:]是深复制&#xff0c;a是浅复制&#xff0c;相当于赋值a的话是赋值了指针&#xff0c;赋值a[:]相当于复制了a对应的那段空间 例如&#xff1a; a [1,1,1,1,1,1]for x in a:if x1:a.remove(x)print(a)运行结果&#xff1a; remove操作是移除序列中第一个x元素。…

Linux系统【二】exec族函数及应用

文件描述符 文件描述符表是一个指针数组&#xff0c;文件描述符是一个整数。 文件描述符表对应的指针是一个结构体&#xff0c;名字为file_struct&#xff0c;里面保存的是已经打开文件的信息 需要注意的是父子进程之间读时共享&#xff0c;写时复制的原则是针对物理地址而言…