1

We have a circuit that is in a shape of a tree. The inputs are the leaf nodes, at the very bottom, and leaf nodes can either be joined by an AND gate or attached to a NOT gate. There is some root node that outputs a final value.

I've been trying to come up with a polynomial-time algorithm to count the number of ways that we can make this circuit evaluate to true. I think that we can use dynamic programming and go top-down, starting with True at the root node and propagating down (i.e. if the gate is a NOT and the value coming into it is a True, then anything under the NOT gate must have been false, etc). However, I'm not sure how we should store the results of previous subproblems - should we also use a tree structure or can we use some kind of 2D array (although it seems like many cells could potentially be unused - and this might be wasteful).

I know that if we removed the restriction of the tree structure this would reduce to Circuit-SAT which is NP-hard. Any help is greatly appreciated!

hologram
  • 533
  • 2
  • 5
  • 21
  • For your problem setup, is an "input" only allowed to be used on a single leaf node? I.e., you can't use a given input more than once in the boolean expression? – lockcmpxchg8b Dec 15 '17 at 06:14

1 Answers1

0

I am assuming that a given input can only appear in one leaf (otherwise the problem again becomes boolean satisfiability--despite the tree structure and restricted operators--by De Morgan's laws).

I believe the answer can be computed without a data-structure. You don't need to enumerate the solutions, afterall. I think the recursion step takes 2 parameters: the node to process, and the target output to be produced by that node, and it returns two values: the number of ways that achieve the target for the node, and the total number of leaves below the node. Both of these can be obtained via a single depth-first traversal over the tree.

Here's pseudo-code with the idea:

(int ways, int leaves) = recurse(Node n, bool target) {
  //Base case of recursion:
  if(n.type == LEAF) {
    return (1, 1);
  }
  else if(n.type == NOT) {
    //for a NOT, we just invert the target.
    return recurse(n.child[0], !target)
  }
  else if (n.type == AND) {
    //AND is more complicated.  First, count the ways that we can make each
    //sub-expression evaluate to true, and the total number of leaves under
    //both the left and right child:
    (int left_true, int left_leaves) = recurse(n.child[0], true);
    (int right_true, int right_leaves) = recurse(n.child[1], true);

    //the total number of ways to make a true AND result is the product of
    //the number of ways to make the left true, and the number of ways to 
    //make the right true.
    int total_true_ways = left_true * right_true;

    //the total number of leaves under this node is the sum of leaves under
    //the left and right subtrees.
    int total_leaves = left_leaves + right_leaves;

    if(target == true) {
      //if this node's target is 'true' we've already computed the number of
      //ways to satisfy that.
      return (total_true_ways, total_leaves);
    }
    else {
      //The number of ways to make a 'false' is the total number of possible
      //input combinations, less the number of input combinations that make 
      //a 'true'.

      //The total number of possible input combinations is given by 2 to the 
      //power of the number of boolean inputs, which is given by the 
      //number of leaves below the node:
      int num_possible_inputs = pow(2, total_leaves);

      return ( num_possible_inputs - total_true_ways, total_leaves);
    }
  }
  else {
    throw internal error
  }
}

Unfortunately, the running time can't be rigorously expressed in the number of leaves because your tree doesn't rule out arbitrarily-long chains of NOT operations. If we assume there are not back-to-back NOT limbs, then a maximal-depth tree is achieved by alternating layers of NOT and AND, leading to a tree with 2*ciel(log_2(n)+1) layers and and 2n + 2(n-1) total nodes. (Where n is the number of leaf nodes.) So a depth-first traversal that touches each node once runs in O(n) under the assumption of no back-to-back NOT operators.

lockcmpxchg8b
  • 2,205
  • 10
  • 16