4

I want to divide a graph with N weighted-vertices and N-1 edges into three parts such that the maximum of the sum of weights of all the vertices in each of the parts is minimized. This is the actual problem i am trying to solve, http://www.iarcs.org.in/inoi/contests/jan2006/Advanced-1.php

I considered the following method

/*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*/

The above algorithm takes O(n^3) time, as the number of edges will be n-1 the outer loop will run (n-1)! times that takes O(n^2) the dfs will visit each vertex only one so that is O(n) time.

But the problem is that n can be <= 3000 and O(n^3) time is not good for this problem. Is there any other method which will calculate the solve the question in the link faster than n^3?

EDIT:

I implemented @BorisStrandjev's algorithm in c, it gave me a correct answer for the test input in the question, but for all other test inputs it gives a wrong answer, here is a link to my code in ideone http://ideone.com/67GSa2, the output here should be 390 but the program prints 395.

I am trying to find if i have made any mistake in my code but i dont see any. Can anyone please help me here the answers my code gave are very close to the correct answer so is there anything more to the algorithm?

EDIT 2:

In the following graph-

enter image description here @BorisStrandjev, your algorithm will chose i as 1, j as 2 in one of the iterations, but then the third part (3,4) is invalid.

EDIT 3

I finally got the mistake in my code, instead of V[i] storing sum of i and all its descendants it stored V[i] and its ancestors, otherwise it would solve the above example correctly, thanks to all of you for your help.

2147483647
  • 1,177
  • 3
  • 13
  • 33
  • 1
    u have a tree? or un-direction graph? – amin k Jan 07 '13 at 11:04
  • its an undirected graph, that has exactly one route between any pair of vertices. It is specified in the link i provided, the question in the link is too long so i did not write in here. – 2147483647 Jan 07 '13 at 11:11
  • I don't quite get your solution with iterating two edges. What exactly do you do with their belonging nodes? Can you please try to explain a bit in further detail? – Boris Strandjev Jan 07 '13 at 11:28
  • @BorisStrandjev As there is only one route between any two nodes, removing two edges create three strongly connected components, we find the sum of vertices of those components using dfs. – 2147483647 Jan 07 '13 at 11:35
  • 1
    @A.06 I may be wrong, but it seems you don't update `V[j]` in `dfs`. Also, I suggest an [adjacency list](http://en.wikipedia.org/wiki/Adjacency_list) or parent variable(s) so you don't have to loop through all the vertices, just recursively all the neighbors / parents. Also, as far as I know, people aren't notified when you edit a post, posting a comment is probably better. – Bernhard Barker Jan 07 '13 at 17:32
  • 1
    I am glad my algorithm worked for you. Now, as you solved your problem I intend to edit my answer and add the pseudocode for the whole implementation for future reference. However I agree with @Dukeling you should not edit your question with such clarification questions. – Boris Strandjev Jan 08 '13 at 09:13
  • 1
    Now I added my pseudoimplementation. You can see if you implemented the remainder of the task as I thought of it. – Boris Strandjev Jan 08 '13 at 10:04

3 Answers3

3

Yes there is faster method.

I will need few auxiliary matrices and I will leave their creation and initialization in correct way to you.

First of all plant the tree - that is make the graph directed. Calculate array VAL[i] for each vertex - the amount of passengers for a vertex and all its descendants (remember we planted, so now this makes sense). Also calculate the boolean matrix desc[i][j] that will be true if vertex i is descendant of vertex j. Then do the following:

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)

After the execution of this cycle the answer will be in best_val. the algorithm is clearly O(n^2) you just need to figure out how to calculate desc[i][j] and VAL[i] in such complexity, but it is not so complex a task, I think you can figure it out yourself.

EDIT Here I will include the code for the whole problem in pseudocode. I deliberately did not include the code before the OP tried and solved it by himself:

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

And the best split will be {descendants of i} \ {descendants of j}, {descendants of j} \ {descendants of i} and {all nodes} \ {descendants of i} U {descendants of j}.

Boris Strandjev
  • 46,145
  • 15
  • 108
  • 135
  • I did not understand how do i make the graph directed, do i treat each input edge a b as an edge from a to b and not b to a? – 2147483647 Jan 07 '13 at 11:54
  • 1
    @A.06 Do a dfs from random vertex. Add as parent to each vertex the vertex you came from. This is classic way of planting undirected graph. – Boris Strandjev Jan 07 '13 at 11:56
  • Also does VAL[i] store the weight of vertex i? – 2147483647 Jan 07 '13 at 12:03
  • 1
    @A.06 Yes, that is my idea. Try to get into the logic of the innermost loop and it will start to make even more sense. – Boris Strandjev Jan 07 '13 at 12:05
  • How does the third part n - val_of_split_i - val_of_split_j satisfies the property that "A and B, are assigned to a particular contractor then all the stations that lie on the route from A to B will also be awarded to the same contractor." – 2147483647 Jan 07 '13 at 12:24
  • @A.06: So what I do is split the tree in three parts - one of them will be rooted in `i`, one of them will be rooted in `j`. I subtract one of them from the other if they are contained in each other. Then the remaining part will be the third contractor. You can verify by yourself that splitting in such way the tree in three parts satisfy the condition you speak about. Basically any subtree will satisfy this condition. – Boris Strandjev Jan 07 '13 at 12:35
1

EDIT 4: THIS WON'T WORK!!!

If you process the nodes in the link in the order 3,4,5,6,1,2, after processing 6, (I think) you'll have the following sets: {{3,4},{5},{6}}, {{3,4,5},{6}}, {{3,4,5,6}}, with no simple way to split them up again.

I'm just leaving this answer here in case anyone else was thinking of a DP algorithm.

It might work to look at all the already processed neighbours in the DP algorithm.

.

I'm thinking a Dynamic Programming algorithm, where the matrix is (item x number of sets)

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

EDIT: Explanation of DP:

This is a reasonably basic Dynamic Programming algorithm. If you need a better explanation, you should read up on it some more, it's a very powerful tool.

A is a matrix. The row i represents a graph with all vertices up to i included. The column c represents the solution with number of sets = c.

So A[2,3] would give the best result of a graph containing item 0, item 1 and item 2 and 3 sets, thus each in it's own set.

You then start at item 0, calculate the row for each number of sets (the only valid one is number of sets = 1), then do item 1 with the above formula, then item 2, etc.

A[a, b] is then the optimal solution with all vertices up to a included and b number of sets. So you'll just return A[k, n] (the one that has all vertices included and the target number of sets).

EDIT 2: Complexity

O(k*n*b) where b is the branching factor of a node (assuming you use an adjacency list).

Since n = 3, this is O(3*k*b) = O(k*b).

EDIT 3: Deciding which neighbouring set a vertex should be added to

Keep n arrays of k elements each in a union find structure, with each set pointing to the sum for that set. For each new row, to determine which sets a vertex can be added to, we use its adjacency list and look-up the set and value of each of its neighbours. Once we find the best option, we can just add that element to the applicable set and increment its sum by the added element's value.

You'll notice the algorithm only looks down 1 row, so we only need to keep track of the last row (not store the whole matrix), and can modify the previous row's n arrays rather than copying them.

Bernhard Barker
  • 54,589
  • 14
  • 104
  • 138
1

You can use a combination of Binary Search & DFS to solve this problem.

Here's how I would proceed:

  1. Calculate the total weight of the graph, and also find the heaviest edge in the graph. Let them be Sum, MaxEdge resp.
  2. Now we have to run a binary search between this range: [maxEdge, Sum].
  3. In each search iteration, middle = (start + end / 2). Now, pick a start node and perform a DFS s.t. the sum of edges traversed in the sub-graph is as close to 'middle' as possible. But keep this sum to be less than middle. This will be one sub graph. In the same iteration, now pick another node which is unmarked by the previous DFS. Perform another DFS in the same way. Likewise, do it once more because we need to break the graph into 3 parts.
  4. The min. weight amongst the 3 sub-graphs calculated above is the solution from this iteration.
  5. Keep running this binary search until its end variable exceeds its start variable.

The max of all the mins obtained in step 4 is your answer. You can do extra book-keeping in order to get the 3-sub-graphs.

Order complexity : N log(Sum) where Sum is the total weight of the graph.

I just noticed that you have talked about weighted vertices, and not edges. In that case, just treat edges as vertices in my solution. It should still work.

  • 1
    I am not able to comment on @Dukeling 's answer due to lack of reputation :P So i have to comment on my own. Here's what i want to ask Dukeling: I think this DP aims to divide any set of numbers into given no. of sets. But since, we are given a graph, we also need to ensure that vertex can be added to a set only if it is connected to any element in that set. Hence it'll require further storage of each set. Won't it complicate things? – prasoonblueluck Jan 07 '13 at 12:25
  • I now realize that that is a little more complex than I first thought and added an explanation. – Bernhard Barker Jan 07 '13 at 13:14
  • In (3), how do you pick a start vertex? The same vertex at every iteration? You say binary search, but you never say how you pick whether to go to the left or right child. – Bernhard Barker Jan 07 '13 at 15:01
  • @Dukeling Pick any vertex. But then always pick the same vertex from there onwards. As for the left/right movement, suppose u picked 5 as middle. Now u tried to divide the graph into 3 parts s.t. each of them is as close to 5, but lesser than 5. U get weights of these graphs as 3,4,5. And still some vertex of graph is uncovered. The u need to go right i.e. set start as 5+1 = 6. But if u suppose u took a large middle .. say 11, u can divide ur graph as 11, 1, 0. So u have covered all vertices but the max is too much. Now go left i.e. put end as 11-1 = 10. – prasoonblueluck Jan 08 '13 at 11:17
  • As in [the link](http://www.iarcs.org.in/inoi/contests/jan2006/Advanced-1.php), if you start at 4, as far as I can see, you algorithm wouldn't work (since either 4 would be alone, or combined with at least 3). – Bernhard Barker Jan 08 '13 at 12:14
  • No, it will combine with either 5 or 6. When it combines with 3, then all nodes are not covered in the 3 sub-graphs. So, we move to the right and choose a bigger number. This bigger number allows us to combine 4 with either 5 or 6. Then we get just 3 subgraphs. And get the right answer. – prasoonblueluck Jan 08 '13 at 13:43
  • Well, at least you can see that there are some non-obvious parts of the algorithm that also deserves a mention. I'm still not convinced it works, but I'll just leave it at that. – Bernhard Barker Jan 08 '13 at 14:36