IOI 2020
IOI 2020

Connecting Supertrees

Given an n n matrix p where p[i][j] \ 0, 1, 2, 3\, construct a simple undirected graph on n nodes such that the number of distinct simple paths between nodes i and j equals p[i][j]. If no such graph exists, report it....

Updated May 21, 2026
Track IOI
Year 2020
Statement Rendered from TeX
TeXC++Rendered statement

Problem Statement

Rendered from the "Problem Summary" section in the LaTeX write-up.

Given an $n \times n$ matrix $p$ where $p[i][j] \in \{0, 1, 2, 3\}$, construct a simple undirected graph on $n$ nodes such that the number of distinct simple paths between nodes $i$ and $j$ equals $p[i][j]$. If no such graph exists, report it.

Key properties:

  • $p[i][j] = 0$: $i$ and $j$ are in different connected components.

  • $p[i][j] = 1$: exactly one simple path (tree edge structure).

  • $p[i][j] = 2$: exactly two simple paths (nodes lie on a simple cycle).

  • $p[i][j] = 3$: exactly three simple paths.

Editorial

The solution write-up is rendered from the LaTeX source, with equations kept live through MathJax.

Solution Approach

Structure Analysis

  • $p[i][j] = 1$ for all pairs in a group implies the group forms a tree.

  • $p[i][j] = 2$ implies the group contains a cycle connecting $i$ and $j$.

  • $p[i][j] = 3$ implies more complex cycle structure (a cycle of cycles or ``bamboo with extra edges'').

Algorithm

  1. Partition into components: Group nodes by $p[i][j] > 0$ using Union-Find. Verify consistency: within a component, all pairs have $p > 0$; between components, all pairs have $p = 0$.

  2. Within each component: Partition further by $p[i][j] = 1$ (must form trees) and $p[i][j] = 2$ (must form cycles).

    • Group nodes where $p[i][j] = 1$ into sub-groups. Within each sub-group, connect them as a tree (path).

    • If there are sub-groups with $p[i][j] = 2$ between them, connect the sub-groups in a cycle by adding one edge from each sub-group to the next.

    • $p[i][j] = 3$ is only possible with at least 3 sub-groups in a meta-cycle, where each meta-node is itself a path of nodes with $p = 1$.

    • Validity checks:

      • $p[i][i]$ must be 1.

      • $p$ must be symmetric.

      • Within a tree group, all pairs must have $p = 1$.

      • A single cycle needs $\ge 3$ nodes.

      • $p[i][j] = 3$ requires $\ge 3$ sub-groups in the cycle.

      • enumerate

      Recursive Construction

      1. For the full component, partition nodes into ``1-groups'' (nodes with $p = 1$ between them) using Union-Find on edges with $p = 1$.

      2. Between different 1-groups in the same component, $p$ must be 2 or 3 (and consistent).

      3. If all inter-group pairs have $p = 2$: connect the 1-groups in a single cycle. Each 1-group internally is a tree (chain). Connect adjacent groups in the cycle with a single edge.

      4. If some pairs have $p = 3$: recursively apply the same logic. Group the 1-groups into ``2-groups'' (groups with $p = 2$ between them), then connect 2-groups in a cycle for $p = 3$.

      C++ Solution

      #include <bits/stdc++.h>
      using namespace std;
      
      struct DSU {
          vector<int> par, rnk;
          DSU(int n) : par(n), rnk(n, 0) { iota(par.begin(), par.end(), 0); }
          int find(int x) { return par[x] == x ? x : par[x] = find(par[x]); }
          bool unite(int a, int b) {
              a = find(a); b = find(b);
              if (a == b) return false;
              if (rnk[a] < rnk[b]) swap(a, b);
              par[b] = a;
              if (rnk[a] == rnk[b]) rnk[a]++;
              return true;
          }
      };
      
      int construct(vector<vector<int>> p) {
          int n = p.size();
          vector<vector<int>> answer(n, vector<int>(n, 0));
      
          // Validate diagonal and symmetry
          for (int i = 0; i < n; i++) {
              if (p[i][i] != 1) return 0;
              for (int j = 0; j < n; j++)
                  if (p[i][j] != p[j][i]) return 0;
          }
      
          // Step 1: Group by p > 0 (connected components)
          DSU comp(n);
          for (int i = 0; i < n; i++)
              for (int j = i + 1; j < n; j++)
                  if (p[i][j] > 0) comp.unite(i, j);
      
          // Verify: within component all p > 0, between components all p = 0
          for (int i = 0; i < n; i++)
              for (int j = i + 1; j < n; j++) {
                  if (comp.find(i) == comp.find(j) && p[i][j] == 0) return 0;
                  if (comp.find(i) != comp.find(j) && p[i][j] != 0) return 0;
              }
      
          // Collect components
          map<int, vector<int>> components;
          for (int i = 0; i < n; i++)
              components[comp.find(i)].push_back(i);
      
          for (auto& [root, nodes] : components) {
              int sz = nodes.size();
              if (sz == 1) continue;
      
              // Step 2: Within component, group by p = 1
              DSU tree_group(n);
              for (int a : nodes)
                  for (int b : nodes)
                      if (p[a][b] == 1) tree_group.unite(a, b);
      
              // Verify within tree groups
              for (int a : nodes)
                  for (int b : nodes)
                      if (tree_group.find(a) == tree_group.find(b) && p[a][b] != 1)
                          return 0;
      
              // Collect tree groups
              map<int, vector<int>> tgroups;
              for (int a : nodes)
                  tgroups[tree_group.find(a)].push_back(a);
      
              // Connect within each tree group as a chain
              for (auto& [tr, tnodes] : tgroups) {
                  for (int i = 0; i + 1 < (int)tnodes.size(); i++) {
                      answer[tnodes[i]][tnodes[i+1]] = 1;
                      answer[tnodes[i+1]][tnodes[i]] = 1;
                  }
              }
      
              // Step 3: Check inter-group p values
              vector<vector<int>> group_list;
              map<int, int> group_id;
              for (auto& [tr, tnodes] : tgroups) {
                  group_id[tr] = group_list.size();
                  group_list.push_back(tnodes);
              }
              int ng = group_list.size();
      
              if (ng == 1) continue; // all p=1, already handled
      
              // Check consistency: between two tree groups, all pairs must have same p value
              vector<vector<int>> inter_p(ng, vector<int>(ng, 0));
              for (int gi = 0; gi < ng; gi++)
                  for (int gj = gi + 1; gj < ng; gj++) {
                      int val = p[group_list[gi][0]][group_list[gj][0]];
                      for (int a : group_list[gi])
                          for (int b : group_list[gj])
                              if (p[a][b] != val) return 0;
                      inter_p[gi][gj] = inter_p[gj][gi] = val;
                  }
      
              // If all inter-group p = 2: connect groups in a single cycle
              bool all_two = true, has_three = false;
              for (int gi = 0; gi < ng; gi++)
                  for (int gj = gi + 1; gj < ng; gj++) {
                      if (inter_p[gi][gj] != 2) all_two = false;
                      if (inter_p[gi][gj] == 3) has_three = true;
                  }
      
              if (all_two) {
                  if (ng < 3) return 0; // cycle needs >= 3 groups (or 2 with multi-edges, not allowed)
                  // Actually, 2 groups can form a cycle if each has >= 1 node
                  // With 2 groups: connect with 2 edges -> but simple graph means at most 1 edge per pair
                  // Need cycle of length >= 3. With 2 groups of size 1 each: 2 nodes, need 2 paths
                  // -> need 2 edges -> multi-edge -> invalid for simple graph
                  // Actually with 2 groups: if group sizes allow, we can form a cycle
                  // of total >= 3 nodes using the chain nodes.
      
                  // Connect groups in a cycle
                  // For each pair of adjacent groups in cycle, add edge from one node in each
                  for (int gi = 0; gi < ng; gi++) {
                      int gj = (gi + 1) % ng;
                      int a = group_list[gi][0];
                      int b = group_list[gj][0];
                      answer[a][b] = answer[b][a] = 1;
                  }
              } else if (has_three) {
                  // Group the tree-groups into "2-groups" by p=2
                  DSU cycle_group(ng);
                  for (int gi = 0; gi < ng; gi++)
                      for (int gj = gi + 1; gj < ng; gj++)
                          if (inter_p[gi][gj] == 2) cycle_group.unite(gi, gj);
      
                  // Verify within 2-group consistency
                  for (int gi = 0; gi < ng; gi++)
                      for (int gj = gi + 1; gj < ng; gj++) {
                          if (cycle_group.find(gi) == cycle_group.find(gj)
                              && inter_p[gi][gj] != 2) return 0;
                          if (cycle_group.find(gi) != cycle_group.find(gj)
                              && inter_p[gi][gj] != 3) return 0;
                      }
      
                  // Collect 2-groups
                  map<int, vector<int>> cgroups;
                  for (int gi = 0; gi < ng; gi++)
                      cgroups[cycle_group.find(gi)].push_back(gi);
      
                  // Within each 2-group: connect tree-groups in a cycle (p=2)
                  for (auto& [cr, gis] : cgroups) {
                      if (gis.size() >= 3) {
                          for (int i = 0; i < (int)gis.size(); i++) {
                              int ni = (i + 1) % gis.size();
                              int a = group_list[gis[i]][0];
                              int b = group_list[gis[ni]][0];
                              answer[a][b] = answer[b][a] = 1;
                          }
                      } else if (gis.size() == 1) {
                          // Single tree group, no cycle needed
                      } else {
                          return 0; // 2 tree groups can't form proper cycle in simple graph generally
                      }
                  }
      
                  // Between 2-groups: connect in a cycle (p=3)
                  vector<int> cgroup_list;
                  for (auto& [cr, gis] : cgroups)
                      cgroup_list.push_back(cr);
      
                  int ncg = cgroup_list.size();
                  if (ncg < 3) return 0;
      
                  for (int i = 0; i < ncg; i++) {
                      int ni = (i + 1) % ncg;
                      auto& g1 = cgroups[cgroup_list[i]];
                      auto& g2 = cgroups[cgroup_list[ni]];
                      int a = group_list[g1[0]][0];
                      int b = group_list[g2[0]][0];
                      answer[a][b] = answer[b][a] = 1;
                  }
              } else {
                  return 0;
              }
          }
      
          // Call build (IOI grader function)
          // build(answer);
          // For local testing, just print
          for (int i = 0; i < n; i++) {
              for (int j = 0; j < n; j++)
                  printf("%d ", answer[i][j]);
              printf("\n");
          }
          return 1;
      }
      
      int main() {
          int n;
          scanf("%d", &n);
          vector<vector<int>> p(n, vector<int>(n));
          for (int i = 0; i < n; i++)
              for (int j = 0; j < n; j++)
                  scanf("%d", &p[i][j]);
          int res = construct(p);
          if (!res) printf("Impossible\n");
          return 0;
      }

      Complexity Analysis

      • Time complexity: $O(n^2)$ for partitioning and validation, $O(n)$ for construction.

      • Space complexity: $O(n^2)$ for the adjacency matrix.

Code

C++ solution used for this page.

C++

Clean code view with a raw-file link when you want the original source.

Raw file
#include <bits/stdc++.h>
using namespace std;

// IOI 2020 - Connecting Supertrees
// Build a graph where the number of simple paths between i and j equals p[i][j].
// p[i][j]=0: different components. p=1: tree. p=2: cycle. p=3: cycle of cycles.

struct DSU {
    vector<int> par, rnk;
    DSU(int n) : par(n), rnk(n, 0) { iota(par.begin(), par.end(), 0); }
    int find(int x) { return par[x] == x ? x : par[x] = find(par[x]); }
    bool unite(int a, int b) {
        a = find(a); b = find(b);
        if (a == b) return false;
        if (rnk[a] < rnk[b]) swap(a, b);
        par[b] = a;
        if (rnk[a] == rnk[b]) rnk[a]++;
        return true;
    }
};

int construct(vector<vector<int>> p) {
    int n = (int)p.size();
    vector<vector<int>> answer(n, vector<int>(n, 0));

    // Validate diagonal and symmetry
    for (int i = 0; i < n; i++) {
        if (p[i][i] != 1) return 0;
        for (int j = 0; j < n; j++)
            if (p[i][j] != p[j][i]) return 0;
    }

    // Step 1: Group by p > 0 (connected components)
    DSU comp(n);
    for (int i = 0; i < n; i++)
        for (int j = i + 1; j < n; j++)
            if (p[i][j] > 0) comp.unite(i, j);

    // Verify component consistency
    for (int i = 0; i < n; i++)
        for (int j = i + 1; j < n; j++) {
            if (comp.find(i) == comp.find(j) && p[i][j] == 0) return 0;
            if (comp.find(i) != comp.find(j) && p[i][j] != 0) return 0;
        }

    // Collect components
    map<int, vector<int>> components;
    for (int i = 0; i < n; i++)
        components[comp.find(i)].push_back(i);

    for (auto& [root, nodes] : components) {
        int sz = (int)nodes.size();
        if (sz == 1) continue;

        // Step 2: Within component, group by p = 1 (tree sub-groups)
        DSU tree_group(n);
        for (int a : nodes)
            for (int b : nodes)
                if (p[a][b] == 1) tree_group.unite(a, b);

        // Verify tree group consistency
        for (int a : nodes)
            for (int b : nodes)
                if (tree_group.find(a) == tree_group.find(b) && p[a][b] != 1)
                    return 0;

        // Collect tree groups and connect each as a chain
        map<int, vector<int>> tgroups;
        for (int a : nodes)
            tgroups[tree_group.find(a)].push_back(a);

        for (auto& [tr, tnodes] : tgroups) {
            for (int i = 0; i + 1 < (int)tnodes.size(); i++) {
                answer[tnodes[i]][tnodes[i + 1]] = 1;
                answer[tnodes[i + 1]][tnodes[i]] = 1;
            }
        }

        // Step 3: Handle inter-group connections
        vector<vector<int>> group_list;
        map<int, int> group_id;
        for (auto& [tr, tnodes] : tgroups) {
            group_id[tr] = (int)group_list.size();
            group_list.push_back(tnodes);
        }
        int ng = (int)group_list.size();
        if (ng == 1) continue;

        // Check consistency between tree groups
        vector<vector<int>> inter_p(ng, vector<int>(ng, 0));
        for (int gi = 0; gi < ng; gi++)
            for (int gj = gi + 1; gj < ng; gj++) {
                int val = p[group_list[gi][0]][group_list[gj][0]];
                for (int a : group_list[gi])
                    for (int b : group_list[gj])
                        if (p[a][b] != val) return 0;
                inter_p[gi][gj] = inter_p[gj][gi] = val;
            }

        // Determine structure type
        bool all_two = true, has_three = false;
        for (int gi = 0; gi < ng; gi++)
            for (int gj = gi + 1; gj < ng; gj++) {
                if (inter_p[gi][gj] != 2) all_two = false;
                if (inter_p[gi][gj] == 3) has_three = true;
            }

        if (all_two) {
            // Connect groups in a single cycle (need >= 3 total nodes)
            if (ng < 3) return 0;
            for (int gi = 0; gi < ng; gi++) {
                int gj = (gi + 1) % ng;
                int a = group_list[gi][0];
                int b = group_list[gj][0];
                answer[a][b] = answer[b][a] = 1;
            }
        } else if (has_three) {
            // Group tree-groups into "2-groups" by p=2, then connect 2-groups in cycle for p=3
            DSU cycle_group(ng);
            for (int gi = 0; gi < ng; gi++)
                for (int gj = gi + 1; gj < ng; gj++)
                    if (inter_p[gi][gj] == 2) cycle_group.unite(gi, gj);

            // Verify 2-group consistency
            for (int gi = 0; gi < ng; gi++)
                for (int gj = gi + 1; gj < ng; gj++) {
                    if (cycle_group.find(gi) == cycle_group.find(gj)
                        && inter_p[gi][gj] != 2) return 0;
                    if (cycle_group.find(gi) != cycle_group.find(gj)
                        && inter_p[gi][gj] != 3) return 0;
                }

            // Collect 2-groups
            map<int, vector<int>> cgroups;
            for (int gi = 0; gi < ng; gi++)
                cgroups[cycle_group.find(gi)].push_back(gi);

            // Within each 2-group: connect tree-groups in a cycle
            for (auto& [cr, gis] : cgroups) {
                if ((int)gis.size() >= 3) {
                    for (int i = 0; i < (int)gis.size(); i++) {
                        int ni = (i + 1) % (int)gis.size();
                        int a = group_list[gis[i]][0];
                        int b = group_list[gis[ni]][0];
                        answer[a][b] = answer[b][a] = 1;
                    }
                } else if ((int)gis.size() == 1) {
                    // Single tree group, no cycle needed
                } else {
                    return 0;
                }
            }

            // Between 2-groups: connect in a meta-cycle for p=3
            vector<int> cgroup_list;
            for (auto& [cr, gis] : cgroups)
                cgroup_list.push_back(cr);
            int ncg = (int)cgroup_list.size();
            if (ncg < 3) return 0;

            for (int i = 0; i < ncg; i++) {
                int ni = (i + 1) % ncg;
                auto& g1 = cgroups[cgroup_list[i]];
                auto& g2 = cgroups[cgroup_list[ni]];
                int a = group_list[g1[0]][0];
                int b = group_list[g2[0]][0];
                answer[a][b] = answer[b][a] = 1;
            }
        } else {
            return 0;
        }
    }

    // Output adjacency matrix
    for (int i = 0; i < n; i++) {
        for (int j = 0; j < n; j++)
            printf("%d ", answer[i][j]);
        printf("\n");
    }
    return 1;
}

int main() {
    int n;
    scanf("%d", &n);
    vector<vector<int>> p(n, vector<int>(n));
    for (int i = 0; i < n; i++)
        for (int j = 0; j < n; j++)
            scanf("%d", &p[i][j]);
    int res = construct(p);
    if (!res) printf("Impossible\n");
    return 0;
}

Source Files and Assets

Raw files are still available here when you want the original TeX, C++, or statement assets.

Show raw files