Skip to content

description: Recognize treewidth at most two and construct a decomposition. Repeatedly remove a live vertex of degree at most two; when it has two neighbors, add the missing edge between them. This reduction is exact: suppressing a degree-two vertex produces a minor, while a decomposition of the filled graph extends by attaching the bag {vertex, first, second}. Reversing the eliminations and attaching each new bag to a later bag that contains its surviving neighbor set yields bags of size at most three.

tree_decomposition_width_two.hpp

SECTIONGraph INCLUDEnoya/tree_decomposition_width_two.hpp

Recognize treewidth at most two and construct a decomposition. Repeatedly remove a live vertex of degree at most two; when it has two neighbors, add the missing edge between them. This reduction is exact: suppressing a degree-two vertex produces a minor, while a decomposition of the filled graph extends by attaching the bag {vertex, first, second}. Reversing the eliminations and attaching each new bag to a later bag that contains its surviving neighbor set yields bags of size at most three.

Verified by tree_decomposition_width_2.

识别并构造宽度不超过 2 的树分解;适合系列并行图或小树宽 DP。

Implementation

View on GitHub

#ifndef NOYA_TREE_DECOMPOSITION_WIDTH_TWO_HPP
#define NOYA_TREE_DECOMPOSITION_WIDTH_TWO_HPP 1

/// @complexity Time: Expected O(V + E).
/// Space: O(V + E).

#include <algorithm>
#include <cassert>
#include <cstdint>
#include <optional>
#include <unordered_map>
#include <unordered_set>
#include <utility>
#include <vector>

namespace noya {

struct width_two_tree_decomposition {
  int width = 0;
  std::vector<std::vector<int>> bags;
  std::vector<std::pair<int, int>> tree_edges;
};

/// @brief Recognize treewidth at most two and construct a decomposition.
/// Repeatedly remove a live vertex of degree at most two; when it has two
/// neighbors, add the missing edge between them. This reduction is exact:
/// suppressing a degree-two vertex produces a minor, while a decomposition of
/// the filled graph extends by attaching the bag {vertex, first, second}.
/// Reversing the eliminations and attaching each new bag to a later bag that
/// contains its surviving neighbor set yields bags of size at most three.
inline std::optional<width_two_tree_decomposition>
tree_decomposition_width_two(
    int vertex_count, const std::vector<std::pair<int, int>> &edges) {
  assert(vertex_count >= 0);
  std::vector<std::vector<int>> graph(vertex_count);
  std::unordered_set<std::uint64_t> present;
  present.reserve(2 * (edges.size() + std::size_t(vertex_count)) + 1);
  auto edge_key = [](int first, int second) {
    if (first > second) {
      std::swap(first, second);
    }
    return (std::uint64_t(std::uint32_t(first)) << 32) |
           std::uint32_t(second);
  };
  for (auto [first, second] : edges) {
    assert(0 <= first && first < vertex_count);
    assert(0 <= second && second < vertex_count);
    assert(first != second);
    bool inserted = present.insert(edge_key(first, second)).second;
    assert(inserted);
    if (!inserted) {
      continue;
    }
    graph[first].push_back(second);
    graph[second].push_back(first);
  }

  std::vector<int> degree(vertex_count);
  std::vector<bool> alive(vertex_count, true);
  std::vector<int> ready;
  ready.reserve(vertex_count + 2 * edges.size());
  for (int vertex = 0; vertex < vertex_count; vertex++) {
    degree[vertex] = int(graph[vertex].size());
    if (degree[vertex] <= 2) {
      ready.push_back(vertex);
    }
  }

  std::vector<std::vector<int>> later_neighbors(vertex_count);
  std::vector<int> elimination_order;
  elimination_order.reserve(vertex_count);
  while (!ready.empty()) {
    int vertex = ready.back();
    ready.pop_back();
    if (!alive[vertex] || degree[vertex] > 2) {
      continue;
    }
    std::vector<int> neighbors;
    for (int next : graph[vertex]) {
      if (alive[next]) {
        neighbors.push_back(next);
      }
    }
    assert(int(neighbors.size()) == degree[vertex]);
    later_neighbors[vertex] = neighbors;
    elimination_order.push_back(vertex);
    alive[vertex] = false;
    for (int next : neighbors) {
      degree[next]--;
      if (degree[next] <= 2) {
        ready.push_back(next);
      }
    }
    if (neighbors.size() == 2) {
      int first = neighbors[0];
      int second = neighbors[1];
      if (present.insert(edge_key(first, second)).second) {
        graph[first].push_back(second);
        graph[second].push_back(first);
        degree[first]++;
        degree[second]++;
      }
    }
  }
  if (int(elimination_order.size()) != vertex_count) {
    return std::nullopt;
  }

  width_two_tree_decomposition result;
  result.bags.resize(vertex_count);
  std::vector<int> bag_containing_vertex(vertex_count, -1);
  std::unordered_map<std::uint64_t, int> edge_bag;
  edge_bag.reserve(2 * (edges.size() + std::size_t(vertex_count)) + 1);

  int created = 0;
  int previous_root = -1;
  for (auto iterator = elimination_order.rbegin();
       iterator != elimination_order.rend(); ++iterator) {
    int vertex = *iterator;
    int bag_id = created++;
    std::vector<int> &bag = result.bags[bag_id];
    bag.push_back(vertex);
    bag.insert(bag.end(), later_neighbors[vertex].begin(),
               later_neighbors[vertex].end());
    result.width = std::max(result.width, int(bag.size()) - 1);

    int parent = -1;
    if (later_neighbors[vertex].size() == 1) {
      parent = bag_containing_vertex[later_neighbors[vertex][0]];
    } else if (later_neighbors[vertex].size() == 2) {
      parent = edge_bag.at(
          edge_key(later_neighbors[vertex][0], later_neighbors[vertex][1]));
    } else if (previous_root != -1) {
      parent = previous_root;
    }
    if (parent != -1) {
      result.tree_edges.emplace_back(parent, bag_id);
    }
    if (later_neighbors[vertex].empty()) {
      previous_root = bag_id;
    }

    for (int member : bag) {
      bag_containing_vertex[member] = bag_id;
    }
    for (int first = 0; first < int(bag.size()); first++) {
      for (int second = first + 1; second < int(bag.size()); second++) {
        edge_bag[edge_key(bag[first], bag[second])] = bag_id;
      }
    }
  }
  return result;
}

} // namespace noya

#endif // NOYA_TREE_DECOMPOSITION_WIDTH_TWO_HPP
#include <algorithm>
#include <cassert>
#include <cstdint>
#include <optional>
#include <unordered_map>
#include <unordered_set>
#include <utility>
#include <vector>

/// @complexity Time: Expected O(V + E).
/// Space: O(V + E).

namespace noya {

struct width_two_tree_decomposition {
  int width = 0;
  std::vector<std::vector<int>> bags;
  std::vector<std::pair<int, int>> tree_edges;
};

/// @brief Recognize treewidth at most two and construct a decomposition.
/// Repeatedly remove a live vertex of degree at most two; when it has two
/// neighbors, add the missing edge between them. This reduction is exact:
/// suppressing a degree-two vertex produces a minor, while a decomposition of
/// the filled graph extends by attaching the bag {vertex, first, second}.
/// Reversing the eliminations and attaching each new bag to a later bag that
/// contains its surviving neighbor set yields bags of size at most three.
inline std::optional<width_two_tree_decomposition>
tree_decomposition_width_two(
    int vertex_count, const std::vector<std::pair<int, int>> &edges) {
  assert(vertex_count >= 0);
  std::vector<std::vector<int>> graph(vertex_count);
  std::unordered_set<std::uint64_t> present;
  present.reserve(2 * (edges.size() + std::size_t(vertex_count)) + 1);
  auto edge_key = [](int first, int second) {
    if (first > second) {
      std::swap(first, second);
    }
    return (std::uint64_t(std::uint32_t(first)) << 32) |
           std::uint32_t(second);
  };
  for (auto [first, second] : edges) {
    assert(0 <= first && first < vertex_count);
    assert(0 <= second && second < vertex_count);
    assert(first != second);
    bool inserted = present.insert(edge_key(first, second)).second;
    assert(inserted);
    if (!inserted) {
      continue;
    }
    graph[first].push_back(second);
    graph[second].push_back(first);
  }

  std::vector<int> degree(vertex_count);
  std::vector<bool> alive(vertex_count, true);
  std::vector<int> ready;
  ready.reserve(vertex_count + 2 * edges.size());
  for (int vertex = 0; vertex < vertex_count; vertex++) {
    degree[vertex] = int(graph[vertex].size());
    if (degree[vertex] <= 2) {
      ready.push_back(vertex);
    }
  }

  std::vector<std::vector<int>> later_neighbors(vertex_count);
  std::vector<int> elimination_order;
  elimination_order.reserve(vertex_count);
  while (!ready.empty()) {
    int vertex = ready.back();
    ready.pop_back();
    if (!alive[vertex] || degree[vertex] > 2) {
      continue;
    }
    std::vector<int> neighbors;
    for (int next : graph[vertex]) {
      if (alive[next]) {
        neighbors.push_back(next);
      }
    }
    assert(int(neighbors.size()) == degree[vertex]);
    later_neighbors[vertex] = neighbors;
    elimination_order.push_back(vertex);
    alive[vertex] = false;
    for (int next : neighbors) {
      degree[next]--;
      if (degree[next] <= 2) {
        ready.push_back(next);
      }
    }
    if (neighbors.size() == 2) {
      int first = neighbors[0];
      int second = neighbors[1];
      if (present.insert(edge_key(first, second)).second) {
        graph[first].push_back(second);
        graph[second].push_back(first);
        degree[first]++;
        degree[second]++;
      }
    }
  }
  if (int(elimination_order.size()) != vertex_count) {
    return std::nullopt;
  }

  width_two_tree_decomposition result;
  result.bags.resize(vertex_count);
  std::vector<int> bag_containing_vertex(vertex_count, -1);
  std::unordered_map<std::uint64_t, int> edge_bag;
  edge_bag.reserve(2 * (edges.size() + std::size_t(vertex_count)) + 1);

  int created = 0;
  int previous_root = -1;
  for (auto iterator = elimination_order.rbegin();
       iterator != elimination_order.rend(); ++iterator) {
    int vertex = *iterator;
    int bag_id = created++;
    std::vector<int> &bag = result.bags[bag_id];
    bag.push_back(vertex);
    bag.insert(bag.end(), later_neighbors[vertex].begin(),
               later_neighbors[vertex].end());
    result.width = std::max(result.width, int(bag.size()) - 1);

    int parent = -1;
    if (later_neighbors[vertex].size() == 1) {
      parent = bag_containing_vertex[later_neighbors[vertex][0]];
    } else if (later_neighbors[vertex].size() == 2) {
      parent = edge_bag.at(
          edge_key(later_neighbors[vertex][0], later_neighbors[vertex][1]));
    } else if (previous_root != -1) {
      parent = previous_root;
    }
    if (parent != -1) {
      result.tree_edges.emplace_back(parent, bag_id);
    }
    if (later_neighbors[vertex].empty()) {
      previous_root = bag_id;
    }

    for (int member : bag) {
      bag_containing_vertex[member] = bag_id;
    }
    for (int first = 0; first < int(bag.size()); first++) {
      for (int second = first + 1; second < int(bag.size()); second++) {
        edge_bag[edge_key(bag[first], bag[second])] = bag_id;
      }
    }
  }
  return result;
}

} // namespace noya