Bridge in a graph that makes the graph disconnected - algorithm

I am working on programming where I need to find the articulation points of a graph (nodes such that removing any of them makes the graph disconnected)
For example, I have these links:
Example 1
[[0,1], [0,2], [1,3], [2,3], [5,6], [3,4]]
The answer should be [2,3,5], because removing these nodes makes the graph disconnected.
Explanation:
If I remove node 2 here, the graph becomes 2 parts 0,1,3,4 and 5,6
If I remove node 3 here, the graph becomes 2 parts 0,1,2,5,6 and 4
If I remove node 5 here, the graph becomes 2 parts 0,1,2,3,4 and 6
Example 2:
[[1,2], [2,3], [3,4], [4,5], [6,3]]
The output should be: [2, 3, 4]
Explanation:
If I remove node 2 here, the graph becomes 2 parts 1, and 3,4,5,6
If I remove node 3 here, the graph becomes 3 parts 1,2 and 6 and 4,5
If I remove node 4 here, the graph becomes 2 parts 1,2,3,6 and 5
How to achieve this in a Java program?

import static java.lang.Math.min;
import java.util.ArrayList;
import java.util.List;
public class ArticulationPointsAdjacencyList {
private int n, id, rootNodeOutcomingEdgeCount;
private boolean solved;
private int[] low, ids;
private boolean[] visited, isArticulationPoint;
private List<List<Integer>> graph;
public ArticulationPointsAdjacencyList(List<List<Integer>> graph, int n) {
if (graph == null || n <= 0 || graph.size() != n) throw new IllegalArgumentException();
this.graph = graph;
this.n = n;
}
// Returns the indexes for all articulation points in the graph even if the
// graph is not fully connected.
public boolean[] findArticulationPoints() {
if (solved) return isArticulationPoint;
id = 0;
low = new int[n]; // Low link values
ids = new int[n]; // Nodes ids
visited = new boolean[n];
isArticulationPoint = new boolean[n];
for (int i = 0; i < n; i++) {
if (!visited[i]) {
rootNodeOutcomingEdgeCount = 0;
dfs(i, i, -1);
isArticulationPoint[i] = (rootNodeOutcomingEdgeCount > 1);
}
}
solved = true;
return isArticulationPoint;
}
private void dfs(int root, int at, int parent) {
if (parent == root) rootNodeOutcomingEdgeCount++;
visited[at] = true;
low[at] = ids[at] = id++;
List<Integer> edges = graph.get(at);
for (Integer to : edges) {
if (to == parent) continue;
if (!visited[to]) {
dfs(root, to, at);
low[at] = min(low[at], low[to]);
if (ids[at] <= low[to]) {
isArticulationPoint[at] = true;
}
} else {
low[at] = min(low[at], ids[to]);
}
}
}
/* Graph helpers */
// Initialize a graph with 'n' nodes.
public static List<List<Integer>> createGraph(int n) {
List<List<Integer>> graph = new ArrayList<>(n);
for (int i = 0; i < n; i++) graph.add(new ArrayList<>());
return graph;
}
// Add an undirected edge to a graph.
public static void addEdge(List<List<Integer>> graph, int from, int to) {
graph.get(from).add(to);
graph.get(to).add(from);
}
/* Example usage: */
public static void main(String[] args) {
testExample2();
}
private static void testExample1() {
int n = 7;
List < List < Integer >> graph = createGraph (n);
addEdge (graph, 0, 1);
addEdge (graph, 0, 2);
addEdge (graph, 1, 3);
addEdge (graph, 2, 3);
addEdge (graph, 2, 5);
addEdge (graph, 5, 6);
addEdge (graph, 3, 4);
ArticulationPointsAdjacencyList solver = new ArticulationPointsAdjacencyList(graph, n);
boolean[] isArticulationPoint = solver.findArticulationPoints();
// Prints:
// Node 2 is an articulation
// Node 3 is an articulation
// Node 5 is an articulation
for (int i = 0; i < n; i++)
if (isArticulationPoint[i]) System.out.printf("Node %d is an articulation\n", i);
}
private static void testExample2() {
int n = 7;
List < List < Integer >> graph = createGraph (n);
addEdge (graph, 1, 2);
addEdge (graph, 2, 3);
addEdge (graph, 3, 4);
addEdge (graph, 3, 6);
addEdge (graph, 4, 5);
ArticulationPointsAdjacencyList solver = new ArticulationPointsAdjacencyList(graph, n);
boolean[] isArticulationPoint = solver.findArticulationPoints();
// Prints:
// Node 2 is an articulation
// Node 3 is an articulation
// Node 4 is an articulation
for (int i = 0; i < n; i++)
if (isArticulationPoint[i]) System.out.printf("Node %d is an articulation\n", i);
}
}
Reference: https://github.com/williamfiset/Algorithms/blob/master/com/williamfiset/algorithms/graphtheory/ArticulationPointsAdjacencyList.java

There are different algorithms used to find the nodes such that if removed they make the graph disconnected (called articulation points).
Here I explain one of them and I provide some code that implements it:
Tarjan Algorithm
Given a graph we want to find all the such that if is removed from the graph become disconnected
The first observation is that the a (weak) connected component in a directed graph is equal to a connected component in the same graph, but where the edges are undirected. So for simplicity we consider as an undirected graph.
Algorithm description
On the graph we run a pre-order Depth First Search (DFS) visit where for any node we assign 2 values, let's call it pre and low. pre represent the instant when the node is visited and low the instant of the lowest reachable node from .
The visit works in this way:
At every step of the visit both pre and low of are set to the next value of pre. Then if we find that a cycle is being closed we set low to pre of the start cycle node. low value is transmitted to parent through DFS backtracking.
When the DFS finish for every couple of nodes such that and are neighbor and low value of is greater or equal to the pre value of then is an articulation point.
For this there is an exception: the root of the DFS spanning tree is an articulation point only if it has more than 1 children
Example
(In the graph P obviously means pre and L means low)
At first pre and low of every vertex are set to a default value (let's say -1)
We start from node 0 and set his pre and low
We go to node 1 and set his pre and low
We can go to 2 or 3, we decide to go to 2 and set his pre and low
We can go to 4 or 5, we decide to go to 4 and set his pre and low
We go to 3 and set his pre and low
We see that 1 is alredy visited; that means it is a cycle, so we update low of 3 to pre of 1
Through backtrack we return to 4 and update his low value
Through backtrack we return to 2 and update his low value
Now we go to 5 and set his pre and low
Through backtrack we return to 2, but there's nothing to do.
We returned from 5 so his low value is fixed and is greater than pre value of 2; so 2 is an articulation point
Through backtrack we return to 1, and there's nothing to do.
We returned from 2 so his low value is fixed and is equal to the pre value of 1; so 1 is an articulation point
Through backtrack we return to 0, but there's nothing to do.
We returned from 1 so his low value is fixed and is greater than pre value of 0; but 0 is the root and has only one child; so it isn't an articulation point
So we have found the answer: [1, 2]
Code
Here is a simple really easy to understand snippet of code (C++) extracted from Competitive Programming Handbook by S. Halim and F. Halim and modified by me.
It is not very adapt to "real word application" (for example because it uses global variables) but it is ok for competitive programming and explaining due to his brevity and clearness.
const int UNVISITED = -1;
vector<int> dfs_low;
vector<int> dfs_pre;
int dfsNumberCounter;
int rootChildren;
vector<vector<int>> AdjList;
vector<int> articulation_vertex;
// This function is the DFS that implement Tarjan algoritm
void articulationPoint(int u) {
dfs_low[u] = dfs_pre[u] = dfsNumberCounter++; // dfs_low[u] <= dfs_pre[u]
for (int j = 0; j < (int)AdjList[u].size(); j++) {
int v = AdjList[u][j];
if (dfs_pre[v] == UNVISITED) { // a tree edge
dfs_parent[v] = u;
if (u == dfsRoot) rootChildren++; // special case if u is a root
articulationPoint(v);
if (dfs_low[v] >= dfs_pre[u]) // for articulation point
articulation_vertex[u] = true; // store this information first
dfs_low[u] = min(dfs_low[u], dfs_low[v]); // update dfs_low[u]
}
else if (v != dfs_parent[u]) // a back edge and not direct cycle
dfs_low[u] = min(dfs_low[u], dfs_pre[v]); // update dfs_low[u]
} }
// Some driver code
int main() {
... //Init of variables and store of the graph inside AdjList is omitted
... // V is the number of nodes
dfsNumberCounter = 0;
dfs_pre.assign(V, UNVISITED);
dfs_low.assign(V, 0);
dfs_parent.assign(V, 0);
articulation_vertex.assign(V, 0);
rootChildren = 0;
articulationPoint(0);
if (root_children > 1) {
articulation_vertex[0] = false;
}
printf("Articulation Points:\n");
for (int i = 0; i < V; i++)
if (articulation_vertex[i])
printf(" Vertex %d\n", i);
}

Related

Find minimal weight of a tree

I am trying to find an algorithm, which can find the minimal total weight of a given tree.
I am given a tree and a weight of all nodes (each node can have a different weight).
For example in this graph, where each node has weight 1:
tree with weights
Then I am given a set of at least two numbers, let's call them X.
For example X: 2, 3, 4, 5.
Each node is assigned one X value, while no two adjacent nodes can have the same X value.
In result, each node has a total weight of X * weight.
After adding total weight of all nodes, we get the total weight of the tree.
tree result
The goal is to find an algorithm, which can find one such distribution of X values, so that we get a minimal weight of the tree.
Any help would be appreciated.
You can use a bottom up approach (through recursion), where for each node you calculate the minimal total weight of the subtree rooted in that node, for each choice of factor (from X) for that node.
So if X has 10 factors, each node will get 10 calculated weights, each one corresponding to a choice of factor.
As you go up one level from a node to its parent, you collect the same information. When looking at one particular child of that parent, take the two minimal weights calculated for that child (from the 10). Let's say they are for factor i and factor j respectively. Then if you make the calculation of the total weight for the parent for factor i, you must take into account the child's weight that corresponds to factor j. In all other cases you can take the one that corresponds to factor i.
Here is the idea expressed in JavaScript:
class Node {
constructor(weight, ...children) {
this.weight = weight;
this.children = children;
}
getMinWeights(factors) {
// Get the node's own weight for each choice of factor:
let weights = [];
for (let i = 0; i < factors.length; i++) {
weights[i] += factors[i] * this.weight);
}
// For each child of this node:
for (let child of this.children) {
// Get the min weight corresponding to each factor-choice
// made for the child node
let childWeights = child.getMinWeights(factors);
// Get positions (i.e. factor indices) of the 2 smallest results
let minIndex1 = 0;
for (let i = 1; i < childWeights.length; i++) {
if (childWeights[i] < childWeights[minIndex1]) {
minIndex1 = i;
}
}
let minIndex2 = minIndex1 > 0 ? 0 : 1;
for (let i = 0; i < childWeights.length; i++) {
if (i !== minIndex1 && childWeights[i] < childWeights[minIndex2]) {
minIndex2 = i;
}
}
// For each factor choice in this node, determine the best choice
// of factor in the child, and add the corresponding weight
// to the total weight for this node's subtree.
for (let i = 0; i < childWeights.length; i++) {
weights[i] += childWeights[i === minIndex1 ? minIndex2 : minIndex1];
}
}
return weights;
}
}
// Example:
let tree = new Node(1,
new Node(1), new Node(1), new Node(1,
new Node(1), new Node(1), new Node(1)
)
);
let result = tree.getMinWeights([2, 3, 4, 5]);
console.log(Math.min(...result)); // Return the minimum of the values we got back.
This algorithm thus has a time complexity of O(nm), where n is the number of nodes, and m = |X|.
When the maximum branching factor b is known, then you can clip X to the b+2 smallest of them (so m = b+2). At any rate X can be clipped to the n smallest values.
Get the distribution of X
The above algorithm can be extended to get an optimal distribution of the X factors. For that the minimal weights (per factor, per node) should be stored for each node. Then a new DFS traversal should find the index having the minimum weight, and assign the corresponding X factor to the node. In recursion that index should be excluded from being assigned to the direct children.
Here is the same code with that extension:
class Node {
constructor(weight, ...children) {
this.weight = weight;
this.children = children;
}
getMinWeights(factors) {
// Get the node's own weight for each choice of factor:
let weights = [];
for (let i = 0; i < factors.length; i++) {
weights[i] += factors[i] * this.weight;
}
// For each child of this node:
for (let child of this.children) {
// Get the min weight corresponding to each factor-choice
// made for the child node
let childWeights = child.getMinWeights(factors);
// Get positions (i.e. factor indices) of the 2 smallest results
let minIndex1 = 0;
for (let i = 1; i < childWeights.length; i++) {
if (childWeights[i] < childWeights[minIndex1]) {
minIndex1 = i;
}
}
let minIndex2 = minIndex1 > 0 ? 0 : 1;
for (let i = 0; i < childWeights.length; i++) {
if (i !== minIndex1 && childWeights[i] < childWeights[minIndex2]) {
minIndex2 = i;
}
}
// For each factor choice in this node, determine the best choice
// of factor in the child, and add the corresponding weight
// to the total weight for this node's subtree.
for (let i = 0; i < childWeights.length; i++) {
weights[i] += childWeights[i === minIndex1 ? minIndex2 : minIndex1];
}
}
// Extra: store the weights with the node
this.weights = weights;
return weights;
}
// Extra: method to distribute the X-factors to each node. Must run after method above.
assignFactors(factors, excludeIndex=-1) {
if (excludeIndex === -1) this.getMinWeights(factors); // First do this...
// Get the index of the factor that results in the minimal weight
let minIndex = excludeIndex === 0 ? 1 : 0;
for (let i = 1; i < this.weights.length; i++) {
if (i !== excludeIndex && this.weights[i] < this.weights[minIndex]) {
minIndex = i;
}
}
// Assign the corresponding factor to this node
this.factor = factors[minIndex];
// For each child of this node:
for (let child of this.children) {
// recurse, and pass the chosen factor index, so it will not be used
// for the child:
child.assignFactors(factors, minIndex);
}
}
toArray() {
return this.children.length ? [this.factor, this.children.map(child => child.toArray())] : this.factor;
}
}
// Example:
let tree = new Node(1,
new Node(1), new Node(1), new Node(1,
new Node(1), new Node(1), new Node(1)
)
);
tree.assignFactors([2, 3, 4, 5]);
console.log(JSON.stringify(tree.toArray()));

Incorrect output in Dijkstra's implementation

I am trying to implement Dijkstra's algorithm using adjacency list and priority queue but getting incorrect output for some of the vertices. Since there is no decreaseKey method in inbuilt priority queue in Java I am adding the new vertex (with updated distance from source). Can anyone advise where am I mistaken ? My code is :
class Graph3 {
private int V;
private ArrayList<Integer> adj [];
Map<String, Integer> weight; //for O(1) lookup of edge weight
PriorityQueue<Vertex> minHeap;
int d [];
int p[];
boolean visited [];
Graph3(int n) {
this.V = n;
adj = new ArrayList[n];
for(int i=0; i<n; i++) {
adj[i] = new ArrayList<Integer>();
}
weight = new HashMap<String, Integer> ();
minHeap = new PriorityQueue<Vertex>(n, new Vertex());
visited = new boolean[n];
Arrays.fill(visited, false);
p = new int[n];
Arrays.fill(p, -1);
d = new int[n];
Arrays.fill(d,Integer.MAX_VALUE);
}
public void addEdge(int a, int b, int w) {
adj[a].add(b);
weight.put(a+","+b,w); //cost of edge(a,b)
}
public void calculateShortest(int source) {
d[source] = 0;
visited[source] = true;
for(int i=0; i<V; i++) minHeap.offer(new Vertex(i,d[i]));
while(!minHeap.isEmpty()) {
Vertex u = minHeap.poll();
relaxEdges(u); //relax all outgoing edges of u
}
for(int i=0; i<d.length; i++) {
System.out.println("Shortest path from "+source+" to vertex "+i+" = "+d[i]);
}
}
public void relaxEdges(Vertex u) {
for(int i: adj[u.getName()]) {
if(!visited[i]) {
int alt = d[u.getName()] + weight.get(u.getName()+","+i);
if(d[i] > alt) {
d[i] = alt;
Vertex temp = new Vertex(i,d[i]);
minHeap.offer(temp);
p[i] = u.getName();
visited[i] = true;
}
}
}
}
}
//to be used for binding every vertex with dval for use in PQ
class Vertex implements Comparator<Vertex> {
int name;
int dval; //current min distance from source
public Vertex() {
}
public Vertex(int name, int dval) {
this.name = name;
this.dval = dval;
}
public int getName() {
return name;
}
public void setName(int name) {
this.name = name;
}
public int getDval() {
return dval;
}
public void setDval(int dval) {
this.dval = dval;
}
public int compare(Vertex a, Vertex b) {
return (a.dval - b.dval);
}
}
public class DijkstraShortestPath {
public static void main(String args []) {
Graph3 g = new Graph3(9);
g.addEdge(0, 1, 4);
g.addEdge(0, 7, 8);
g.addEdge(1, 2, 8);
g.addEdge(1, 7, 11);
g.addEdge(2, 3, 7);
g.addEdge(2, 8, 2);
g.addEdge(2, 5, 4);
g.addEdge(3, 4, 9);
g.addEdge(3, 5, 14);
g.addEdge(4, 5, 10);
g.addEdge(5, 6, 2);
g.addEdge(6, 7, 1);
g.addEdge(6, 8, 6);
g.addEdge(7, 8, 7);
g.calculateShortest(0);
}
}
**My Output :**
Shortest path from 0 to vertex 0 = 0
Shortest path from 0 to vertex 1 = 4
Shortest path from 0 to vertex 2 = 12
Shortest path from 0 to vertex 3 = 19
Shortest path from 0 to vertex 4 = 28
Shortest path from 0 to vertex 5 = 16
Shortest path from 0 to vertex 6 = 18
Shortest path from 0 to vertex 7 = 8
Shortest path from 0 to vertex 8 = 15
**Correct Output :**
Shortest path from 0 to vertex 0 = 0
Shortest path from 0 to vertex 1 = 4
Shortest path from 0 to vertex 2 = 12
Shortest path from 0 to vertex 3 = 19
Shortest path from 0 to vertex 4 = 21
Shortest path from 0 to vertex 5 = 11
Shortest path from 0 to vertex 6 = 9
Shortest path from 0 to vertex 7 = 8
Shortest path from 0 to vertex 8 = 14
Something that is off is that you set visited[i] = true for all neighboring nodes that get updated in relaxEdges. Dijkstra's algorithm always only sets the currently processed node to visited, not the neighbors. I would guess that this is why you get incorrect output. Delete this line and instead add visited[u.getName()] = true in the while loop.
Since you add nodes multiple times to the queue, you should also directly test against visited[u.getName()] in the while-loop so nodes do not get processed multiple times.
Then, you assign to p[i], but never use it.
And lastly, I advice to have an Edge class because representing edges as string-concatenated integers sure is clunky.

Shortest path to visit all nodes

I'm looking for an algorithm that seems very typical to me, but it seems that the common solutions are all just a little bit different.
In an undirected graph, I want the shortest path that visits every node. Nodes can be revisited and I do not have to return to the start node.
The Travelling Salesman Problem seems to add the restriction that each node can only be visited once and that the path has to return to where it started.
Minimal Spanning Trees may be part of a solution, but such algorithms only provide the tree, not a minimal path. Additionally, because they're trees and therefore have no loops, they force backtracking where a loop may be more efficient.
You can reduce it to the normal Travelling Salesman Problem by transforming the graph.
First, compute the minimum distance for every pair of nodes. You can use Floyd-Warshall algorithm for that. Once you have it, just construct the complete graph where the edge between nodes u and v is the minimum cost from u to v.
Then, you can apply a normal TSP algorithm as you don't have to revisit nodes anymore, that's already hidden in the costs of the edges.
We can use a modified BFS.
Basically from any node during a BFS we need to be able to traverse nodes already travelled but how do we make sure we're not forming infinite cycles.
We store visited state for "ALL" nodes from each node, what this means is if we've walked over node 1 and we need to traverse back over it we can as long as our total state of "ALL" nodes has not been seen before. This is the reason for the bitmask and not a simple Set of Integers. Note you can use a Set of Strings to store the state as well it just runs a slower.
public int shortestPathInSmallGraph(int[][] graph) {
if (graph.length == 1) {
return 0;
}
Set<Integer>[] adj = new HashSet[graph.length];
int n = graph.length;
int endState = (1 << n) - 1;
boolean[][] seen = new boolean[n][endState];
Queue<int[]> queue = new ArrayDeque<>();
for (int i = 0; i < n; i++) {
queue.add(new int[] {i, 1 << i});
seen[i][1 << i] = true;
}
int steps = 0;
while (!queue.isEmpty()) {
int count = queue.size();
for (int i = 0; i < count; i++) {
int[] pair = queue.poll();
int node = pair[0];
int state = pair[1];
for (int neighbor : graph[node]) {
int nextState = state | (1 << neighbor);
if (nextState == endState) {
return 1 + steps;
}
if (!seen[neighbor][nextState]) {
seen[neighbor][nextState] = true;
queue.add(new int[] {neighbor, nextState});
}
}
}
steps++;
}
return -1;
}

Topological sorting for a directed acyclic graph

Is it possible to have different topological sorts for a directed acyclic graph G? For example in a graph:
A --> B --> D
B --> E
A --> C --> E
I thought topological sorts depend on the finishing times of each vertex after running a depth-first-search algorithm. Isn't each finishing time unique and thus only one topological sort for G is possible?
yes. you can traverse the graph in multiple ways.
In your example, you can have A, B ,.... or A, C, ......
The wikipedia page on topological sorting has a better example:
http://en.wikipedia.org/wiki/Topological_sorting
From the wiki page cited above:
If a topological sort has the property that all pairs of consecutive
vertices in the sorted order are connected by edges, then these edges
form a directed Hamiltonian path in the DAG. If a Hamiltonian path
exists, the topological sort order is unique; no other order respects
the edges of the path. Conversely, if a topological sort does not form
a Hamiltonian path, the DAG will have two or more valid topological
orderings, for in this case it is always possible to form a second
valid ordering by swapping two consecutive vertices that are not
connected by an edge to each other. Therefore, it is possible to test
in linear time whether a unique ordering exists, and whether a
Hamiltonian path exists, despite the NP-hardness of the Hamiltonian
path problem for more general directed graphs (Vernet & Markenzon
1997).
public class TopologicalSort
{
private int V; // No. of vertices
private List<int> [] adj; // Adjacency List
public ToplogicalSort(int v)
{
V = v;
adj = new List<int>[v];
for (int i=0; i < v; ++i)
adj[i] = new List<int>();
}
public void AddEdge(int v,int w) { adj[v].Add(w); }
public void TopologicalSortUtil(int v, bool[] visited, Stack<int> stack)
{
Stack<int> stackTracing = new Stack<int>();
bool res = true;
List<int> list = new List<int>(adj[v]);
stackTracing.Push(v);
while (stackTracing.Count != 0 | res)
{
int n = stackTracing.Peek();
list = new List<int>(adj[n]);
bool check = false;
foreach (var elem in list)
{
if (!visited[elem])
{
visited[elem] = true;
n = elem;
stackTracing.Push(elem);
check = true;
break;
}
}
if(!check)
{
if(!stack.Contains(n))
{
stack.Push(n);
}
if (stackTracing.Count != 0)
stackTracing.Pop();
res = false;
}
}
}
public void TopologicalSort()
{
Stack<int> stack = new Stack<int>();
bool[] visited = new bool[V];
for (int i = 0; i < V; i++)
visited[i] = false;
for (int i = 0; i < V; i++)
{
if (visited[i] == false)
{
topologicalSortUtil(i, visited, stack);
}
}
// Print contents of stack
while (stack.Count != 0)
{
stack.Pop();
Console.WriteLine(stack.Pop() + " ");
}
}
public static void RunSort()
{
TopologicalSort g = new TopologicalSort(6);
g.AddEdge(5, 2);
g.AddEdge(5, 0);
g.AddEdge(4, 0);
g.AddEdge(4, 1);
g.AddEdge(2, 3);
g.AddEdge(3, 1);
g.TopologicalSort();
}
}

Subtrees of a tree

I have a given tree with n nodes. The task is to find the number of subtrees of the given tree with outgoing edges to its complement less than or equal to a given number K.
for example: If n=3 and k=1
and the given tree is 1---2---3
Then the total valid subtrees would be 6
{}, {1}, {3}, {1,2}, {2,3}, {1,2,3}
I know I can enumerate all 2^n trees and chack the valid ones, but is there some approach that is faster? Can I achieve polynomial time in n? Something close to O(n^3) or even O(n^4) would be nice.
EDIT: for k=1 this value turns out to be 2*n
This is a fairly typical instance of the DP-on-a-tree paradigm. Let's generalize the problem slightly by allowing the specification of a root vertex v and stratifying the counts of the small-boundary trees in two ways: whether v is included, and how many edges comprise the boundary.
The base case is easy. There are no edges and thus two subtrees: one includes v, the other excludes v, and both have no boundary edges. Otherwise, let e = {v, w} be an edge incident to v. The instance looks like this.
|\ /|
| \ e / |
|L v-----w R|
| / \ |
|/ \|
Compute recursively the stratified counts for L rooted at v and R rooted at w.
Subtrees that include v consist of a subtree in L that includes v, plus optionally e and a subtree in R that includes w. Subtrees that don't include v consist of either a subtree in L that doesn't include v, or a subtree in R (double counting the empty tree). This means we can obtain the stratified counts by convolving the stratified counts for L with the stratified counts for R.
Here's how this works on your example. Let's choose root 1.
e
1---2---3
We choose e as shown and recurse.
1
The vector for includes-1 is [1], since the one subtree is {1}, with no boundary. The vector for excludes-1 is [1], since the one subtree is {}, also with no boundary.
2---3
We compute 2 and 3 as we did for 1. The vector for includes-2 is [1, 1], since {2, 3} has no boundary edges, and {2} has one. We obtained this vector by adding the includes-2 vector for 2, shifted by one because of the new boundary edge to make [0, 1], to the convolution of the includes-2 vector for 2 with the includes-3 vector for 3, which is [1, 0]. The vector for excludes-2 is [1] + [1, 1] - [1] = [1, 1], where [1, 1] is the sum of the shifted includes-3 vector and the excludes-3 vector, and the subtraction is to compensate for double-counting {}.
Now, for the original invocation, to get the includes-1 vector, we add [0, 1], the includes-1 vector for 1 shifted by one, to the convolution of [1] with [1, 1], obtaining [1, 2]. To check: {1, 2, 3} has no boundary, and {1} and {1, 2} have one boundary edge. The excludes-1 vector is [1] + [1, 2, 1] - [1] = [1, 2, 1]. To check: {} has no boundary, {2, 3} and {3} have one boundary edge, and {2} has two boundary edges.
Here is my python implementation of David Eisenstat's solution:
from sys import stdin
from numpy import *
from scipy import *
def roundup_pow2(x):
"""
Round up to power of 2 (obfuscated and unintentionally faster :).
"""
while x&(x-1):
x = (x|(x>>1))+1
return max(x,1)
def to_long(x):
return long(rint(x))
def poly_mul(a,b):
n = len(a) + len(b) - 1
nr = roundup_pow2(n)
a += [0L]*(nr-len(a))
b += [0L]*(nr-len(b)) # pad with zeros to length n
u = fft(a)
v = fft(b)
w = ifft(u*v)[:n].real # ifft == inverse fft
return map(to_long,w)
def pad(l,s) :
return l+[0L]*(s-len(l))
def make_tree(l,x,y):
l[x][y]=y
l[x].pop(y)
for child in l[x]:
make_tree(l,child,x)
def cut_tree(l,x) :
if len(l[x])==0:
return [1L],[1L]
y,_ = l[x].popitem()
ai,ax=cut_tree(l,x)
bi,bx=cut_tree(l,y)
ci=[0L]+ai
tmp=poly_mul(ai,bi)
padlen=max(len(ci),len(tmp))
ci=pad(ci,padlen)
tmp=pad(tmp,padlen)
ci=map(add,ci,tmp)
cx=[0L]+bi
padlen=max(len(cx),len(bx),len(ax))
cx=pad(cx,padlen)
bx=pad(bx,padlen)
ax=pad(ax,padlen)
tmp=pad([-1],padlen)
cx=map(add,cx,bx)
cx=map(add,cx,ax)
cx=map(add,cx,tmp)
return ci,cx
n,k = map(int,raw_input().split())
l=[{}]
for i in range(1,n+1):
d={}
l.append(d)
for i in range(1,n):
x,y = map(int,raw_input().split())
l[x][y]=y
l[y][x]=x
make_tree(l,1,0)
i,x = cut_tree(l,1)
padlen=max(len(i),len(x))
i=pad(i,padlen)
x=pad(x,padlen)
combined=map(add,i,x)
sum=0L
for i in range(0,k+1) :
sum+=combined[i]
print sum
Let us create a slightly bigger tree like below.
1
/ | \
2 3 \
/ 4
7 / \
5 6
Let us define a function F(a, k) for each node 'a' with 'k' edges removed from node 'a' and below.
i.e. if 'k' edges are removed from node 'a' then we create F(a, k) number of subtrees.
(If 'a' is not root, it is assumed to be connected to it's parent).
e.g. in above tree ( F(4, 1) = 2 ), as we create 2 trees by removing 2 edges below '4'
(we assume that 4 is connected to parent and subtrees (5) and (6) are not counted in F(4,1))
We traverse and calculate 'F' of each child first. Then using child's F we calculate
parents F.
F(a, k) of a leaf node is '0' for all k
For non-leaf nodes.
F(a, k) = SUM (F(child, k)) + Z
While F(child, k) can be calculated recursively.
Z on the other hand is calculated by finding all combinations where some child take
ri edges out of k such that SUM(ri) = k
Programmatically this can be done by fixing 'j' edge for a given child and then
calculating the number of trees created by distributing 'k-j' edges to other children.
e.g. in above tree
F(1, 3) = F(2, 3) + F(3, 3) + F(4, 3) + // we pass k as-is to child
F(2,1)*F(3,1)*F(4,1) + F(2,1)*F(3,2) + F(2,1)*F(4,2) + //consume 1 edge by 2 and distribute 2 to other children
F(2, 2)*F(3,1) + F(2,2)*F(4,1) + // consume 2 edges from node '2' and 1 for other children
F(3,1)*F(4,2)
As we see above, we fix 'r' edge for node 2 and then distribute '3-r' edges to other children.
We keep doing this for all children of '1'.
Additionally, we create sub-trees when we detach a node from parent.
e.g. in above case when we calculate F(1, 3) we create the following
detached trees.
detached_tree += F(2, 2) + F(3, 2) + F(4, 2)
Here we assume that one edge is consumed by detaching child node from parent,
and in child node if we consume 'k-1' edges we will create F(child, k-1) subtrees.
These trees are counted and stored seperately in detached_trees.
Once we have calculated the F(a,k) of all nodes.
The total subtrees are 'SUM(F(root, k)) for all k' + 'total nodes - 1' + detached_trees.
We add 'total nodes - 1' to our total. This is because when a node (except root) is detached
from a tree, it creates two trees with 1 edge missing. While one of the tree is counted
in F(parent, 1), the other is not counted anywhere, hence needs to be counted in total.
Here is C code of above algorithm. The recursion can be further optimized.
#define MAX 51
/* We use the last entry of alist to store number of children of a given node */
#define NUM_CHILD(alist, node) (alist[node][MAX])
int alist[MAX][MAX+1] = {0};
long F[MAX][MAX]={0};
long detached_subtrees = 0;
/*
* We fix one of the child node for 'i' edges out of 'n', then we traverse
* over the rest of the children to get 'n-i' edges, we do so recursivly.
* Note that if 'n' is 1, we can always build a subtree by detaching.
*/
long REST_OF_NODES_SUM(int node, int q, int n)
{
long sum = 0, i, node2, ret = 0, nd;
/* fix node2 and calcualte the subtree for rest of the children */
for(nd = q; nd < NUM_CHILD(alist, node); nd++) {
node2 = alist[node][nd];
/* Consume 'i' edges and send 'n-i' for other children of node */
for (i = 1; i < n ; i++) {
sum = REST_OF_NODES_SUM(node, nd + 1, n - i);
ret += (F[node2][i] * sum);
/* Add one for 'node2' getting detached from tree */
if (i == 1) { ret += sum; }
}
ret += F[node2][n];
/* If only one edge is to be consumed, we detach 'node2' from the tree */
if (n == 1) { ret++; }
}
return ret;
}
void get_counts(int N, int K, int node, int root)
{
int child_node;
int i, j, p, k;
if (NUM_CHILD(alist, node) == 0) { return; }
for(i = 0 ; i < NUM_CHILD(alist, node); i++) {
child_node = alist[node][i];
/* Do a recursive traversal of all children */
get_counts(N, K, child_node, node);
F[node][1] += (F[child_node][1]);
}
F[node][1] += NUM_CHILD(alist, node);
for (k = 2; k <= K; k++) {
for(p = 0; p < NUM_CHILD(alist, node); p++) {
child_node = alist[node][p];
F[node][k] += F[child_node][k];
/* If we remove this child, then we create subtrees in the child */
detached_subtrees += F[child_node][k-1];
/* Assume that 'child_node' is detached, find tree created by rest
* of children for 'k-j' edges */
F[node][k] += REST_OF_NODES_SUM(node, p + 1, k - 1);
/* Fix one child node for 'j' edges out of 'k' and traverse over the rest of
* children for 'k - j' edges */
for (j = 1; j < k ; j++) {
if (F[child_node][j]) F[node][k] += (F[child_node][j] * REST_OF_NODES_SUM(node, p + 1, k - j));
}
}
}
}
void remove_back_ref(int parent, int node)
{
int c;
for (c = 0; c < NUM_CHILD(alist, node); c++) {
if (alist[node][c] == parent) {
if ((c + 1) == NUM_CHILD(alist, node)) {
NUM_CHILD(alist, node)--;
alist[node][c] = 0;
} else {
/* move last entry here */
alist[node][c] = alist[node][NUM_CHILD(alist, node)-1];
alist[node][NUM_CHILD(alist, node)-1] = 0;
NUM_CHILD(alist, node)--;
}
}
}
}
/* go to each child and remove back links */
void normalize(int node)
{
int j, child;
for (j = 0; j < NUM_CHILD(alist, node); j++) {
child = alist[node][j];
remove_back_ref(node, child);
normalize(child);
}
}
long cutTree(int N, int K, int edges_rows, int edges_columns, int** edges)
{
int i, j;
int node, index;
long ret = 0;
/* build an adjacency list from the above edges */
for (i = 0; i < edges_rows; i++) {
alist[edges[i][0]][NUM_CHILD(alist, edges[i][0])] = edges[i][1];
alist[edges[i][1]][NUM_CHILD(alist, edges[i][1])] = edges[i][0];
NUM_CHILD(alist, edges[i][0])++;
NUM_CHILD(alist, edges[i][1])++;
}
/* get rid of the back links in children */
normalize(1);
get_counts(N, K, 1, 1);
for (i = 1; i <= K; i++) { ret += F[1][i]; }
/* Every node (except root) when detached from tree, will create one extra subtree. */
ret += (N - 1);
/* The subtrees created by detaching from parent */
ret += detached_subtrees;
/* Add two for empty and full tree */
ret += 2;
return ret;
}
main(int argc, char *argv[])
{
int **arr;
int ret, i, N, K, x, y;
scanf("%d%d", &N, &K);
arr = malloc((N - 1) * sizeof(int*));
for (i = 0; i < (N - 1); i++) { arr[i] = malloc(2*sizeof(int)); }
for (i = 0; i < N-1; i++) { scanf("%d%d", &x, &y); arr[i][0] = x; arr[i][1] = y; }
printf("MAX %d ret %ld\n", MAX, cutTree(N, K, N-1, 2, arr));
}

Resources