Skip to content

bipartite_edge_coloring.hpp

SECTIONGraph INCLUDEnoya/bipartite_edge_coloring.hpp

Color every edge of a bipartite multigraph with Delta colors so incident edges differ. Low-degree vertices are contracted before regularization, keeping the number of real and dummy edges linear.

Verified by bipartite_edge_coloring.

用最大度种颜色给二分多重图的边染色,使相邻边颜色不同。

Implementation

View on GitHub

#ifndef NOYA_BIPARTITE_EDGE_COLORING_HPP
#define NOYA_BIPARTITE_EDGE_COLORING_HPP 1

/// @complexity Time: O(E sqrt(V) log Delta) with Euler splitting and sparse
/// perfect matchings; Space: O(V + E).

#include <algorithm>
#include <cassert>
#include <functional>
#include <numeric>
#include <queue>
#include <utility>
#include <vector>

namespace noya {
namespace bipartite_edge_coloring_internal {

struct dsu {
  std::vector<int> parent;

  explicit dsu(int size) : parent(size, -1) {}

  int leader(int vertex) {
    if (parent[vertex] < 0) {
      return vertex;
    }
    return parent[vertex] = leader(parent[vertex]);
  }

  int merge(int first, int second) {
    first = leader(first);
    second = leader(second);
    if (first == second) {
      return first;
    }
    if (parent[first] > parent[second]) {
      std::swap(first, second);
    }
    parent[first] += parent[second];
    parent[second] = first;
    return first;
  }
};

inline std::pair<std::vector<int>, int>
contract_vertices(const std::vector<int> &degree, int maximum_degree) {
  const int size = int(degree.size());
  dsu components(size);
  std::priority_queue<std::pair<int, int>> queue;
  for (int vertex = 0; vertex < size; vertex++) {
    queue.emplace(-degree[vertex], vertex);
  }
  while (queue.size() > 1) {
    auto [negative_first, first] = queue.top();
    queue.pop();
    auto [negative_second, second] = queue.top();
    queue.pop();
    if (-negative_first - negative_second > maximum_degree) {
      break;
    }
    int root = components.merge(first, second);
    queue.emplace(negative_first + negative_second, root);
  }

  std::vector<int> root_id(size, -1);
  std::vector<int> result(size);
  int count = 0;
  for (int vertex = 0; vertex < size; vertex++) {
    int root = components.leader(vertex);
    if (root_id[root] == -1) {
      root_id[root] = count++;
    }
    result[vertex] = root_id[root];
  }
  return {result, count};
}

struct edge_record {
  int left;
  int right;
  int original_id;
};

inline std::vector<int>
perfect_matching(int size, const std::vector<edge_record> &all_edges,
                 const std::vector<int> &edge_ids) {
  std::vector<std::vector<int>> adjacency(size);
  for (int id : edge_ids) {
    adjacency[all_edges[id].left].push_back(id);
  }
  std::vector<int> match_left(size, -1);
  std::vector<int> match_right(size, -1);
  std::vector<int> distance(size);
  std::vector<int> cursor(size);

  while (true) {
    std::queue<int> queue;
    std::fill(distance.begin(), distance.end(), -1);
    std::fill(cursor.begin(), cursor.end(), 0);
    for (int left = 0; left < size; left++) {
      if (match_left[left] == -1) {
        distance[left] = 0;
        queue.push(left);
      }
    }
    while (!queue.empty()) {
      int left = queue.front();
      queue.pop();
      for (int id : adjacency[left]) {
        int next_id = match_right[all_edges[id].right];
        if (next_id == -1) {
          continue;
        }
        int next_left = all_edges[next_id].left;
        if (distance[next_left] == -1) {
          distance[next_left] = distance[left] + 1;
          queue.push(next_left);
        }
      }
    }

    std::function<bool(int)> augment = [&](int left) {
      for (int &index = cursor[left]; index < int(adjacency[left].size());
           index++) {
        int id = adjacency[left][index];
        int right = all_edges[id].right;
        int next_id = match_right[right];
        if (next_id == -1 ||
            (distance[all_edges[next_id].left] == distance[left] + 1 &&
             augment(all_edges[next_id].left))) {
          match_left[left] = id;
          match_right[right] = id;
          return true;
        }
      }
      distance[left] = -1;
      return false;
    };

    int augmented = 0;
    for (int left = 0; left < size; left++) {
      if (match_left[left] == -1) {
        augmented += augment(left);
      }
    }
    if (augmented == 0) {
      break;
    }
  }
  for (int id : match_left) {
    assert(id != -1);
  }
  return match_left;
}

inline std::pair<std::vector<int>, std::vector<int>>
split_even_regular(int size, const std::vector<edge_record> &all_edges,
                   const std::vector<int> &edge_ids) {
  std::vector<std::vector<int>> adjacency(2 * size);
  for (int id : edge_ids) {
    adjacency[all_edges[id].left].push_back(id);
    adjacency[size + all_edges[id].right].push_back(id);
  }
  std::vector<int> cursor(2 * size);
  std::vector<bool> used(all_edges.size());
  std::vector<int> first;
  std::vector<int> second;
  first.reserve(edge_ids.size() / 2);
  second.reserve(edge_ids.size() / 2);

  for (int start = 0; start < 2 * size; start++) {
    while (cursor[start] < int(adjacency[start].size()) &&
           used[adjacency[start][cursor[start]]]) {
      cursor[start]++;
    }
    if (cursor[start] == int(adjacency[start].size())) {
      continue;
    }

    std::vector<int> vertex_stack{start};
    std::vector<int> edge_stack;
    std::vector<int> circuit;
    while (!vertex_stack.empty()) {
      int vertex = vertex_stack.back();
      while (cursor[vertex] < int(adjacency[vertex].size()) &&
             used[adjacency[vertex][cursor[vertex]]]) {
        cursor[vertex]++;
      }
      if (cursor[vertex] == int(adjacency[vertex].size())) {
        vertex_stack.pop_back();
        if (!edge_stack.empty()) {
          circuit.push_back(edge_stack.back());
          edge_stack.pop_back();
        }
        continue;
      }
      int id = adjacency[vertex][cursor[vertex]++];
      if (used[id]) {
        continue;
      }
      used[id] = true;
      int next = vertex < size ? size + all_edges[id].right
                               : all_edges[id].left;
      vertex_stack.push_back(next);
      edge_stack.push_back(id);
    }
    assert(circuit.size() % 2 == 0);
    for (int index = 0; index < int(circuit.size()); index++) {
      (index & 1 ? second : first).push_back(circuit[index]);
    }
  }
  assert(first.size() == edge_ids.size() / 2);
  assert(second.size() == edge_ids.size() / 2);
  return {std::move(first), std::move(second)};
}

inline void color_regular(int size, const std::vector<edge_record> &all_edges,
                          std::vector<int> edge_ids, int degree, int offset,
                          std::vector<int> &result) {
  if (degree == 0) {
    assert(edge_ids.empty());
    return;
  }
  assert(edge_ids.size() == std::size_t(size) * degree);
  if (degree == 1) {
    for (int id : edge_ids) {
      if (all_edges[id].original_id != -1) {
        result[all_edges[id].original_id] = offset;
      }
    }
    return;
  }
  if (degree % 2 == 0) {
    auto [first, second] = split_even_regular(size, all_edges, edge_ids);
    color_regular(size, all_edges, std::move(first), degree / 2, offset,
                  result);
    color_regular(size, all_edges, std::move(second), degree / 2,
                  offset + degree / 2, result);
    return;
  }

  std::vector<int> matching = perfect_matching(size, all_edges, edge_ids);
  std::vector<bool> selected(all_edges.size());
  for (int id : matching) {
    selected[id] = true;
    if (all_edges[id].original_id != -1) {
      result[all_edges[id].original_id] = offset;
    }
  }
  std::vector<int> remaining;
  remaining.reserve(edge_ids.size() - size);
  for (int id : edge_ids) {
    if (!selected[id]) {
      remaining.push_back(id);
    }
  }
  color_regular(size, all_edges, std::move(remaining), degree - 1,
                offset + 1, result);
}

} // namespace bipartite_edge_coloring_internal

/// @brief Color every edge of a bipartite multigraph with Delta colors so
/// incident edges differ. Low-degree vertices are contracted before
/// regularization, keeping the number of real and dummy edges linear.
inline std::vector<int>
bipartite_edge_coloring(int left_size, int right_size,
                        const std::vector<std::pair<int, int>> &edges) {
  using namespace bipartite_edge_coloring_internal;
  assert(left_size >= 0 && right_size >= 0);
  std::vector<int> left_degree(left_size);
  std::vector<int> right_degree(right_size);
  int maximum_degree = 0;
  for (auto [left, right] : edges) {
    assert(0 <= left && left < left_size);
    assert(0 <= right && right < right_size);
    maximum_degree = std::max(maximum_degree, ++left_degree[left]);
    maximum_degree = std::max(maximum_degree, ++right_degree[right]);
  }
  if (edges.empty()) {
    return {};
  }

  auto [left_id, left_count] =
      contract_vertices(left_degree, maximum_degree);
  auto [right_id, right_count] =
      contract_vertices(right_degree, maximum_degree);
  int size = std::max(left_count, right_count);
  std::vector<int> contracted_left_degree(size);
  std::vector<int> contracted_right_degree(size);
  std::vector<edge_record> all_edges;
  all_edges.reserve(edges.size() * 2);
  for (int id = 0; id < int(edges.size()); id++) {
    int left = left_id[edges[id].first];
    int right = right_id[edges[id].second];
    all_edges.push_back({left, right, id});
    contracted_left_degree[left]++;
    contracted_right_degree[right]++;
  }

  int left = 0;
  int right = 0;
  while (left < size && right < size) {
    while (left < size && contracted_left_degree[left] == maximum_degree) {
      left++;
    }
    while (right < size && contracted_right_degree[right] == maximum_degree) {
      right++;
    }
    if (left == size || right == size) {
      break;
    }
    int count = std::min(maximum_degree - contracted_left_degree[left],
                         maximum_degree - contracted_right_degree[right]);
    for (int index = 0; index < count; index++) {
      all_edges.push_back({left, right, -1});
    }
    contracted_left_degree[left] += count;
    contracted_right_degree[right] += count;
  }
  assert(std::all_of(contracted_left_degree.begin(),
                     contracted_left_degree.end(),
                     [&](int degree) { return degree == maximum_degree; }));
  assert(std::all_of(contracted_right_degree.begin(),
                     contracted_right_degree.end(),
                     [&](int degree) { return degree == maximum_degree; }));

  std::vector<int> edge_ids(all_edges.size());
  std::iota(edge_ids.begin(), edge_ids.end(), 0);
  std::vector<int> result(edges.size(), -1);
  color_regular(size, all_edges, std::move(edge_ids), maximum_degree, 0,
                result);
  for (int color : result) {
    assert(0 <= color && color < maximum_degree);
  }
  return result;
}

} // namespace noya

#endif // NOYA_BIPARTITE_EDGE_COLORING_HPP
#include <algorithm>
#include <cassert>
#include <functional>
#include <numeric>
#include <queue>
#include <utility>
#include <vector>

/// @complexity Time: O(E sqrt(V) log Delta) with Euler splitting and sparse
/// perfect matchings; Space: O(V + E).

namespace noya {
namespace bipartite_edge_coloring_internal {

struct dsu {
  std::vector<int> parent;

  explicit dsu(int size) : parent(size, -1) {}

  int leader(int vertex) {
    if (parent[vertex] < 0) {
      return vertex;
    }
    return parent[vertex] = leader(parent[vertex]);
  }

  int merge(int first, int second) {
    first = leader(first);
    second = leader(second);
    if (first == second) {
      return first;
    }
    if (parent[first] > parent[second]) {
      std::swap(first, second);
    }
    parent[first] += parent[second];
    parent[second] = first;
    return first;
  }
};

inline std::pair<std::vector<int>, int>
contract_vertices(const std::vector<int> &degree, int maximum_degree) {
  const int size = int(degree.size());
  dsu components(size);
  std::priority_queue<std::pair<int, int>> queue;
  for (int vertex = 0; vertex < size; vertex++) {
    queue.emplace(-degree[vertex], vertex);
  }
  while (queue.size() > 1) {
    auto [negative_first, first] = queue.top();
    queue.pop();
    auto [negative_second, second] = queue.top();
    queue.pop();
    if (-negative_first - negative_second > maximum_degree) {
      break;
    }
    int root = components.merge(first, second);
    queue.emplace(negative_first + negative_second, root);
  }

  std::vector<int> root_id(size, -1);
  std::vector<int> result(size);
  int count = 0;
  for (int vertex = 0; vertex < size; vertex++) {
    int root = components.leader(vertex);
    if (root_id[root] == -1) {
      root_id[root] = count++;
    }
    result[vertex] = root_id[root];
  }
  return {result, count};
}

struct edge_record {
  int left;
  int right;
  int original_id;
};

inline std::vector<int>
perfect_matching(int size, const std::vector<edge_record> &all_edges,
                 const std::vector<int> &edge_ids) {
  std::vector<std::vector<int>> adjacency(size);
  for (int id : edge_ids) {
    adjacency[all_edges[id].left].push_back(id);
  }
  std::vector<int> match_left(size, -1);
  std::vector<int> match_right(size, -1);
  std::vector<int> distance(size);
  std::vector<int> cursor(size);

  while (true) {
    std::queue<int> queue;
    std::fill(distance.begin(), distance.end(), -1);
    std::fill(cursor.begin(), cursor.end(), 0);
    for (int left = 0; left < size; left++) {
      if (match_left[left] == -1) {
        distance[left] = 0;
        queue.push(left);
      }
    }
    while (!queue.empty()) {
      int left = queue.front();
      queue.pop();
      for (int id : adjacency[left]) {
        int next_id = match_right[all_edges[id].right];
        if (next_id == -1) {
          continue;
        }
        int next_left = all_edges[next_id].left;
        if (distance[next_left] == -1) {
          distance[next_left] = distance[left] + 1;
          queue.push(next_left);
        }
      }
    }

    std::function<bool(int)> augment = [&](int left) {
      for (int &index = cursor[left]; index < int(adjacency[left].size());
           index++) {
        int id = adjacency[left][index];
        int right = all_edges[id].right;
        int next_id = match_right[right];
        if (next_id == -1 ||
            (distance[all_edges[next_id].left] == distance[left] + 1 &&
             augment(all_edges[next_id].left))) {
          match_left[left] = id;
          match_right[right] = id;
          return true;
        }
      }
      distance[left] = -1;
      return false;
    };

    int augmented = 0;
    for (int left = 0; left < size; left++) {
      if (match_left[left] == -1) {
        augmented += augment(left);
      }
    }
    if (augmented == 0) {
      break;
    }
  }
  for (int id : match_left) {
    assert(id != -1);
  }
  return match_left;
}

inline std::pair<std::vector<int>, std::vector<int>>
split_even_regular(int size, const std::vector<edge_record> &all_edges,
                   const std::vector<int> &edge_ids) {
  std::vector<std::vector<int>> adjacency(2 * size);
  for (int id : edge_ids) {
    adjacency[all_edges[id].left].push_back(id);
    adjacency[size + all_edges[id].right].push_back(id);
  }
  std::vector<int> cursor(2 * size);
  std::vector<bool> used(all_edges.size());
  std::vector<int> first;
  std::vector<int> second;
  first.reserve(edge_ids.size() / 2);
  second.reserve(edge_ids.size() / 2);

  for (int start = 0; start < 2 * size; start++) {
    while (cursor[start] < int(adjacency[start].size()) &&
           used[adjacency[start][cursor[start]]]) {
      cursor[start]++;
    }
    if (cursor[start] == int(adjacency[start].size())) {
      continue;
    }

    std::vector<int> vertex_stack{start};
    std::vector<int> edge_stack;
    std::vector<int> circuit;
    while (!vertex_stack.empty()) {
      int vertex = vertex_stack.back();
      while (cursor[vertex] < int(adjacency[vertex].size()) &&
             used[adjacency[vertex][cursor[vertex]]]) {
        cursor[vertex]++;
      }
      if (cursor[vertex] == int(adjacency[vertex].size())) {
        vertex_stack.pop_back();
        if (!edge_stack.empty()) {
          circuit.push_back(edge_stack.back());
          edge_stack.pop_back();
        }
        continue;
      }
      int id = adjacency[vertex][cursor[vertex]++];
      if (used[id]) {
        continue;
      }
      used[id] = true;
      int next = vertex < size ? size + all_edges[id].right
                               : all_edges[id].left;
      vertex_stack.push_back(next);
      edge_stack.push_back(id);
    }
    assert(circuit.size() % 2 == 0);
    for (int index = 0; index < int(circuit.size()); index++) {
      (index & 1 ? second : first).push_back(circuit[index]);
    }
  }
  assert(first.size() == edge_ids.size() / 2);
  assert(second.size() == edge_ids.size() / 2);
  return {std::move(first), std::move(second)};
}

inline void color_regular(int size, const std::vector<edge_record> &all_edges,
                          std::vector<int> edge_ids, int degree, int offset,
                          std::vector<int> &result) {
  if (degree == 0) {
    assert(edge_ids.empty());
    return;
  }
  assert(edge_ids.size() == std::size_t(size) * degree);
  if (degree == 1) {
    for (int id : edge_ids) {
      if (all_edges[id].original_id != -1) {
        result[all_edges[id].original_id] = offset;
      }
    }
    return;
  }
  if (degree % 2 == 0) {
    auto [first, second] = split_even_regular(size, all_edges, edge_ids);
    color_regular(size, all_edges, std::move(first), degree / 2, offset,
                  result);
    color_regular(size, all_edges, std::move(second), degree / 2,
                  offset + degree / 2, result);
    return;
  }

  std::vector<int> matching = perfect_matching(size, all_edges, edge_ids);
  std::vector<bool> selected(all_edges.size());
  for (int id : matching) {
    selected[id] = true;
    if (all_edges[id].original_id != -1) {
      result[all_edges[id].original_id] = offset;
    }
  }
  std::vector<int> remaining;
  remaining.reserve(edge_ids.size() - size);
  for (int id : edge_ids) {
    if (!selected[id]) {
      remaining.push_back(id);
    }
  }
  color_regular(size, all_edges, std::move(remaining), degree - 1,
                offset + 1, result);
}

} // namespace bipartite_edge_coloring_internal

/// @brief Color every edge of a bipartite multigraph with Delta colors so
/// incident edges differ. Low-degree vertices are contracted before
/// regularization, keeping the number of real and dummy edges linear.
inline std::vector<int>
bipartite_edge_coloring(int left_size, int right_size,
                        const std::vector<std::pair<int, int>> &edges) {
  using namespace bipartite_edge_coloring_internal;
  assert(left_size >= 0 && right_size >= 0);
  std::vector<int> left_degree(left_size);
  std::vector<int> right_degree(right_size);
  int maximum_degree = 0;
  for (auto [left, right] : edges) {
    assert(0 <= left && left < left_size);
    assert(0 <= right && right < right_size);
    maximum_degree = std::max(maximum_degree, ++left_degree[left]);
    maximum_degree = std::max(maximum_degree, ++right_degree[right]);
  }
  if (edges.empty()) {
    return {};
  }

  auto [left_id, left_count] =
      contract_vertices(left_degree, maximum_degree);
  auto [right_id, right_count] =
      contract_vertices(right_degree, maximum_degree);
  int size = std::max(left_count, right_count);
  std::vector<int> contracted_left_degree(size);
  std::vector<int> contracted_right_degree(size);
  std::vector<edge_record> all_edges;
  all_edges.reserve(edges.size() * 2);
  for (int id = 0; id < int(edges.size()); id++) {
    int left = left_id[edges[id].first];
    int right = right_id[edges[id].second];
    all_edges.push_back({left, right, id});
    contracted_left_degree[left]++;
    contracted_right_degree[right]++;
  }

  int left = 0;
  int right = 0;
  while (left < size && right < size) {
    while (left < size && contracted_left_degree[left] == maximum_degree) {
      left++;
    }
    while (right < size && contracted_right_degree[right] == maximum_degree) {
      right++;
    }
    if (left == size || right == size) {
      break;
    }
    int count = std::min(maximum_degree - contracted_left_degree[left],
                         maximum_degree - contracted_right_degree[right]);
    for (int index = 0; index < count; index++) {
      all_edges.push_back({left, right, -1});
    }
    contracted_left_degree[left] += count;
    contracted_right_degree[right] += count;
  }
  assert(std::all_of(contracted_left_degree.begin(),
                     contracted_left_degree.end(),
                     [&](int degree) { return degree == maximum_degree; }));
  assert(std::all_of(contracted_right_degree.begin(),
                     contracted_right_degree.end(),
                     [&](int degree) { return degree == maximum_degree; }));

  std::vector<int> edge_ids(all_edges.size());
  std::iota(edge_ids.begin(), edge_ids.end(), 0);
  std::vector<int> result(edges.size(), -1);
  color_regular(size, all_edges, std::move(edge_ids), maximum_degree, 0,
                result);
  for (int color : result) {
    assert(0 <= color && color < maximum_degree);
  }
  return result;
}

} // namespace noya