图形质心是等距离或距离小于或等于(N / 2)的顶点,其中N是通过此顶点连接的连通分量的大小?! [需要更正?!]
这是CodeForces的一个问题,它要求查找每个顶点是否为质心,但是在一次删除和替换一个边之后。
我需要帮助来改进这种伪码/算法。
Loop all Vertices: Loop all edges: Position each edge in every empty edge position between two unconnected nodes Count Size of each Connected Component (*1). If all sizes are less than or equal N/2, Then return true
问题是该算法将至少运行O(N * M ^ 2))。这是不可接受的。
我查了解答案,但我无法想出其他人使用的算法的高级抽象。您能帮我理解这些解决方案的工作原理吗?
(* 1)DFS Loop
答案 0 :(得分:0)
我将尝试描述一个在线性时间内解决此问题的不太复杂的算法,以便将来参考我的code(它有一些注释)。
主要思想是你可以将树T置于任意顶点并遍历它,对于每个顶点V,你可以这样做:
以前的算法可以仔细实现以获得线性时间复杂度,问题在于它有很多情况需要处理。
更好的想法是在顶点C找到T的质心C和根T.
将顶点C作为T的根是有用的,因为它保证了C的每个后代都具有 当遍历树时,我们可以避免检查树中最重的顶点但是向上,每次我们访问一个孩子W时,如果我们重新生根T,我们可以传递最重的大小(<= N / 2)在W。 尝试理解我解释的内容,如果有什么不清楚,请告诉我。
答案 1 :(得分:0)
嗯,树的质心可以用O(N)空间和时间复杂度来确定。
构造表示树的矩阵,其中行索引表示N个节点,第i行中的元素表示第i个节点所连接的节点。您也可以使用任何其他表示形式。
维护2个大小为N的线性数组,索引i分别代表第i个节点(深度)的深度和第i个节点(父节点)的父节点。
还要维护另外两个线性数组,第一个包含树(队列)的BFS遍历序列,另一个包含的值[leftOver] [N - 节点中的节点数以该节点为根的子树。换句话说,第i个索引包含当从树中删除第i个节点及其所有子节点时整个树中剩余的节点数。
现在,以root身份执行任意节点的BFS遍历,并填充数组'parent'和'depth'。这需要O(N)时间复杂度。另外,在数组'队列'中记录遍历序列。
从叶节点开始,添加以该节点为根的子树中存在的节点数,其值为数组“leftOver”中的父索引。这也需要O(N)时间,因为你可以使用已经准备好的'队列'数组并从后面旅行。
最后,遍历数组'leftOver'并将每个值修改为[N-1 - 初始值]。准备好'leftOver'数组。费用:另一个O(N)。
你的工作差不多完成了。现在,迭代这个'leftOver'数组并找到其值最接近floor(N / 2)的索引。但是,该值不得以任何代价超过最低限额(N / 2)。
此索引是树的质心的索引。总时间复杂度:O(N)。
Java代码:
import java.util.ArrayList;
import java.util.Iterator;
import java.util.Scanner;
class Find_Centroid
{
static final int MAXN=100_005;
static ArrayList<Integer>[] graph;
static int[] depth,parent; // Step 2
static int N;
static Scanner io=new Scanner(System.in);
public static void main(String[] args)
{
int i;
N=io.nextInt();
// Number of nodes in the Tree
graph=new ArrayList[N];
for(i=0;i<graph.length;++i)
graph[i]=new ArrayList<>();
//Initialisation
for(i=1;i<N;++i)
{
int a=io.nextInt()-1,b=io.nextInt()-1;
// Assuming 1-based indexing
graph[a].add(b); graph[b].add(a);
// Step 1
}
int centroid = findCentroid(new java.util.Random().nextInt(N));
// Arbitrary indeed... ;)
System.out.println("Centroid: "+(centroid+1));
// '+1' for output in 1-based index
}
static int[] queue=new int[MAXN],leftOver;
// Step 3
static int findCentroid(int r)
{
leftOver=new int[N];
int i,target=N/2,ach=-1;
bfs(r); // Step 4
for(i=N-1;i>=0;--i)
if(queue[i]!=r)
leftOver[parent[queue[i]]] += leftOver[queue[i]] +1;
// Step 5
for(i=0;i<N;++i)
leftOver[i] = N-1 -leftOver[i];
// Step 6
for(i=0;i<N;++i)
if(leftOver[i]<=target && leftOver[i]>ach)
// Closest to target(=N/2) but does not exceed it.
{
r=i; ach=leftOver[i];
}
// Step 7
return r;
}
static void bfs(int root) // Iterative
{
parent=new int[N]; depth=new int[N];
int st=0,end=0;
parent[root]=-1; depth[root]=1;
// Parent of root is obviously undefined. Hence -1.
// Assuming depth of root = 1
queue[end++]=root;
while(st<end)
{
int node = queue[st++], h = depth[node]+1;
Iterator<Integer> itr=graph[node].iterator();
while(itr.hasNext())
{
int ch=itr.next();
if(depth[ch]>0) // 'ch' is parent of 'node'
continue;
depth[ch]=h; parent[ch]=node;
queue[end++]=ch; // Recording the Traversal sequence
}
}
}
}
现在,对于这个问题,http://codeforces.com/contest/709/problem/E,遍历每个节点i,将其视为根,继续下降具有> N / 2个节点的子节点,并尝试到达一个刚刚拥有的节点在它下面少于N / 2个节点(最接近N / 2个节点)。如果在删除此节点及其所有子节点时使'i'成为质心,则打印'1'否则打印0.此过程可以有效执行,因为'leftOver'数组已经存在。
实际上,您正在分离令人不安的节点(阻止我成为质心的节点)及其子节点并将其附加到第i个节点本身。子树保证最多有N / 2个节点(如前所述),因此现在不会出现问题。
快乐编码..... :)