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....
Source-first archive entry
This page is built from the copied files in competitive_programming/ioi/2020/connecting_supertrees. Edit
competitive_programming/ioi/2020/connecting_supertrees/solution.tex to update the written solution and
competitive_programming/ioi/2020/connecting_supertrees/solution.cpp to update the implementation.
The website does not replace those files with hand-maintained HTML. It reads the copied source tree during the build and exposes the exact files below.
Problem Statement
Rendered from the "Problem Summary" section inside solution.tex because this entry has no separate statement file.
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
Rendered from the copied solution.tex file. The original TeX source remains
available below.
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
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$.
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
For the full component, partition nodes into ``1-groups'' (nodes with $p = 1$ between them) using Union-Find on edges with $p = 1$.
Between different 1-groups in the same component, $p$ must be 2 or 3 (and consistent).
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.
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
Exact copied C++ implementation from solution.cpp.
#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
Exact copied source-of-truth files. Edit solution.tex for the write-up and solution.cpp for the implementation.
\documentclass[11pt,a4paper]{article}
\usepackage[utf8]{inputenc}
\usepackage[T1]{fontenc}
\usepackage{amsmath,amssymb,amsthm}
\usepackage{listings}
\usepackage{geometry}
\usepackage{hyperref}
\geometry{margin=2.5cm}
\lstset{
language=C++,
basicstyle=\ttfamily\small,
keywordstyle=\bfseries,
breaklines=true,
numbers=left,
numberstyle=\tiny,
frame=single,
tabsize=2
}
\title{IOI 2020 -- Connecting Supertrees}
\author{Solution}
\date{}
\begin{document}
\maketitle
\section{Problem Summary}
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:
\begin{itemize}
\item $p[i][j] = 0$: $i$ and $j$ are in different connected components.
\item $p[i][j] = 1$: exactly one simple path (tree edge structure).
\item $p[i][j] = 2$: exactly two simple paths (nodes lie on a simple cycle).
\item $p[i][j] = 3$: exactly three simple paths.
\end{itemize}
\section{Solution Approach}
\subsection{Structure Analysis}
\begin{itemize}
\item $p[i][j] = 1$ for all pairs in a group implies the group forms a tree.
\item $p[i][j] = 2$ implies the group contains a cycle connecting $i$ and $j$.
\item $p[i][j] = 3$ implies more complex cycle structure (a cycle of cycles or ``bamboo with extra edges'').
\end{itemize}
\subsection{Algorithm}
\begin{enumerate}
\item \textbf{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$.
\item \textbf{Within each component:} Partition further by $p[i][j] = 1$ (must form trees) and $p[i][j] = 2$ (must form cycles).
\begin{itemize}
\item Group nodes where $p[i][j] = 1$ into sub-groups. Within each sub-group, connect them as a tree (path).
\item 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.
\item $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$.
\end{itemize}
\item \textbf{Validity checks:}
\begin{itemize}
\item $p[i][i]$ must be 1.
\item $p$ must be symmetric.
\item Within a tree group, all pairs must have $p = 1$.
\item A single cycle needs $\ge 3$ nodes.
\item $p[i][j] = 3$ requires $\ge 3$ sub-groups in the cycle.
\end{itemize}
\end{enumerate}
\subsection{Recursive Construction}
\begin{enumerate}
\item For the full component, partition nodes into ``1-groups'' (nodes with $p = 1$ between them) using Union-Find on edges with $p = 1$.
\item Between different 1-groups in the same component, $p$ must be 2 or 3 (and consistent).
\item 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.
\item 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$.
\end{enumerate}
\section{C++ Solution}
\begin{lstlisting}
#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;
}
\end{lstlisting}
\section{Complexity Analysis}
\begin{itemize}
\item \textbf{Time complexity:} $O(n^2)$ for partitioning and validation, $O(n)$ for construction.
\item \textbf{Space complexity:} $O(n^2)$ for the adjacency matrix.
\end{itemize}
\end{document}
#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;
}