将图形分成三个部分,使得三个部分的权重之和的最大值最小化

时间:2013-01-07 11:00:17

标签: c algorithm graph graph-algorithm depth-first-search

我想将具有N个加权顶点和N-1个边的图分成三个部分,使得每个部分中所有顶点的权重之和的最大值最小化。这是我想要解决的实际问题,http://www.iarcs.org.in/inoi/contests/jan2006/Advanced-1.php

我考虑了以下方法

/*Edges are stored in an array E, and also in an adjacency matrix for depth first search.
Every edge in E has two attributes a and b which are the nodes of the edge*/
min-max = infinity
for i -> 0 to length(E):
   for j -> i+1 to length(E):
     /*Call depth first search on the nodes of both the edges E[i] and E[j]
     the depth first search returns the sum of weights of the vertices it visits,
     we keep track of the maximum weight returned by dfs*/
     Adjacency-matrix[E[i].a][E[i].b] = 0;
     Adjacency-matrix[E[j].a][E[j].b] = 0;
     max = 0
     temp = dfs(E[i].a) 
     if temp > max then max = temp
     temp = dfs(E[i].b) 
     if temp > max then max = temp
     temp = dfs(E[i].a) 
     if temp > max then max = temp
     temp = dfs(E[i].a) 
     if temp > max then max = temp

     if max < min-max
        min-max = max
     Adjacency-matrix[E[i].a][E[i].b] = 1;
     Adjacency-matrix[E[j].a][E[j].b] = 1;
    /*The depth first search is called four times but it will terminate one time
   if we keep track of the visited vertices because there are only three components*/

  /*After the outer loop terminates what we have in min-max will be the answer*/

上述算法需要O(n ^ 3)时间,因为外部循环将运行的边数为n-1(n-1)!采用O(n ^ 2)dfs的时间将只访问每个顶点一次,这样就是O(n)时间。

但问题是n可能<= 3000且O(n ^ 3)时间对此问题不利。有没有其他方法能够比n ^ 3更快地计算解决链接中的问题?

编辑:

我在c中实现了@ BorisStrandjev的算法,它给出了问题中测试输入的正确答案,但对于所有其他测试输入,它给出了错误的答案,这是我在ideone http://ideone.com/67GSa2中的代码的链接,此处的输出应为390,但程序打印395.

我试图找到我的代码中是否犯了任何错误,但我没有看到任何错误。任何人都可以请帮助我这里我的代码给出的答案非常接近正确的答案所以还有什么更多的算法?

编辑2:

在下图中 -

enter image description here @BorisStrandjev,你的算法会在其中一个迭代中选择i为1,j为2,但第三部分(3,4)无效。

编辑3

我终于在我的代码中遇到了错误,而不是V [i]存储了i及其所有后代的总和,它存储了V [i]及其祖先,否则它会正确地解决上面的例子,感谢你们所有人您的帮助。

3 个答案:

答案 0 :(得分:3)

是的,有更快的方法。

我需要很少的辅助矩阵,我会以正确的方式将它们的创建和初始化留给你。

首先植树 - 这是图表的定向。为每个顶点计算数组VAL[i] - 顶点及其所有后代的乘客数量(记住我们种植了,所以现在这是有道理的)。如果顶点desc[i][j]是顶点i的后代,也计算布尔矩阵j。然后执行以下操作:

best_val = n
for i in 1...n
  for j in i + 1...n
    val_of_split = 0
    val_of_split_i = VAL[i]
    val_of_split_j = VAL[j]
    if desc[i][j] val_of_split_j -= VAL[i] // subtract all the nodes that go to i
    if desc[j][i] val_of_split_i -= VAL[j]
    val_of_split = max(val_of_split, val_of_split_i)
    val_of_split = max(val_of_split, val_of_split_j)
    val_of_split = max(val_of_split, n - val_of_split_i - val_of_split_j)
    best_val  = min(best_val, val_of_split)

执行此循环后,答案将在best_val。算法显然O(n^2)你需要弄清楚如何计算desc[i][j]VAL[i]这样的复杂性,但它不是那么复杂的任务,我想你可以自己搞清楚

编辑这里我将在伪代码中包含整个问题的代码。我故意在OP尝试之前没有包含代码并自行解决:

int p[n] := // initialized from the input - price of the node itself
adjacency_list neighbors := // initialized to store the graph adjacency list

int VAL[n] := { 0 } // the price of a node and all its descendants
bool desc[n][n] := { false } // desc[i][j] - whether i is descendant of j

boolean visited[n][n] := {false} // whether the dfs visited the node already
stack parents := {empty-stack}; // the stack of nodes visited during dfs

dfs ( currentVertex ) {
   VAL[currentVertex] = p[currentVertex]
   parents.push(currentVertex)
   visited[currentVertex] = true
   for vertex : parents // a bit extended stack definition supporting iteration
       desc[currentVertex][vertex] = true
   for vertex : adjacency_list[currentVertex]
       if visited[vertex] continue
       dfs (currentvertex)
       VAL[currentVertex] += VAL[vertex]
   perents.pop

calculate_best ( )
    dfs(0)
    best_val = n
    for i in 0...(n - 1)
      for j in i + 1...(n - 1)
        val_of_split = 0
        val_of_split_i = VAL[i]
        val_of_split_j = VAL[j]
        if desc[i][j] val_of_split_j -= VAL[i]
        if desc[j][i] val_of_split_i -= VAL[j]
        val_of_split = max(val_of_split, val_of_split_i)
        val_of_split = max(val_of_split, val_of_split_j)
        val_of_split = max(val_of_split, n - val_of_split_i - val_of_split_j)
        best_val  = min(best_val, val_of_split)
    return best_val

最佳分割将是{descendants of i} \ {descendants of j}{descendants of j} \ {descendants of i}{all nodes} \ {descendants of i} U {descendants of j}

答案 1 :(得分:1)

编辑4:这将无效!!!

如果您按照3,4,5,6,1,2的顺序处理the link中的节点,则在处理6之后(我认为)您将拥有以下集合:{{3,4 },{5},{6}},{{3,4,5},{6}},{{3,4,5,6}},没有简单的方法可以再次拆分它们。

我只是留下这个答案,以防其他人想到DP算法。

可能需要查看DP算法中所有已处理的邻居。

我正在考虑动态编程算法,其中矩阵是(项目x组数)

n = number of sets
k = number of vertices
// row 0 represents 0 elements included
A[0, 0] = 0
for (s = 1:n)
  A[0, s] = INFINITY
for (i = 1:k)
  for (s = 0:n)
    B = A[i-1, s] with i inserted into minimum one of its neighbouring sets
    A[i, s] = min(A[i-1, s-1], B)) // A[i-1, s-1] = INFINITY if s-1 < 0

编辑:DP说明:

这是一个相当基本的Dynamic Programming算法。如果你需要更好的解释,你应该再读一遍,这是一个非常强大的工具。

A是一个矩阵。行i表示包含所有顶点的图形。列c表示具有组数= c的解。

所以A[2,3]会给出包含项目0,项目1和项目2和3集合的图表的最佳结果,因此每个集合都在它自己的集合中。

然后从项目0开始,计算每组数量的行(唯一有效的一组是数量= 1),然后使用上面的公式,然后是项目2,等等。

A[a, b]是最佳解决方案,所有顶点都包含在包含和b个集合中。因此,您只需返回A[k, n](包含所有顶点的那个和目标套数)。

编辑2:复杂性

O(k*n*b)其中b是节点的分支因子(假设您使用adjacency list)。

n = 3起,这是O(3*k*b) = O(k*b)

编辑3:决定应将哪个邻居集添加到

将k个元素的n个数组分别保存在union find结构中,每个数组都指向该集合的总和。对于每个新行,要确定可以添加顶点的集合,我们使用其adjacency list并查找每个邻居的集合和值。一旦找到最佳选项,我们就可以将该元素添加到适用的集合中,并通过添加的元素值增加其总和。

你会注意到算法只向下看1行,所以我们只需要跟踪最后一行(不存储整个矩阵),并且可以修改前一行的n个数组而不是复制它们。

答案 2 :(得分:1)

您可以使用 Binary Search&amp; amp;组合DFS 解决了这个问题。

以下是我将如何继续:

  1. 计算图表的总重量,并在图表中找到最重的边缘。让他们成为Sum,MaxEdge resp。
  2. 现在我们必须在此范围之间运行二进制搜索:[maxEdge,Sum]。
  3. 在每次搜索迭代中,中间=(开始+结束/ 2)。现在,选择一个开始节点并执行DFS s.t.子图中遍历的边的总和尽可能接近“中间”。但保持这个数字不到中等。这将是一个子图。在同一次迭代中,现在选择另一个未被前一个DFS标记的节点。以相同的方式执行另一个DFS。同样,再次这样做是因为我们需要将图形分为3个部分。
  4. 分钟。上面计算的3个子图中的权重是这次迭代的解。
  5. 继续运行此二进制搜索,直到其结束变量超过其起始变量。
  6. 在步骤4中获得的所有分钟的最大值是您的答案。 您可以进行额外的簿记以获得3个子图。

    订单复杂度:N log(Sum)其中Sum是图表的总权重。

    我刚刚注意到你谈到了加权顶点,而不是边缘。在这种情况下,只需将边缘视为我的解决方案中的顶点。它应该仍然有效。