IOI 2013 - Dreaming
Given a forest (collection of trees) with N nodes and M edges (each with a weight), you can add edges of weight L to connect the trees into a single tree. Minimize the diameter (longest shortest path between any two n...
Source-first archive entry
This page is built from the copied files in competitive_programming/ioi/2013/dreaming. Edit
competitive_programming/ioi/2013/dreaming/solution.tex to update the written solution and
competitive_programming/ioi/2013/dreaming/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 a forest (collection of trees) with $N$ nodes and $M$ edges (each with a weight), you can add edges of weight $L$ to connect the trees into a single tree. Minimize the diameter (longest shortest path between any two nodes) of the resulting tree.
Editorial
Rendered from the copied solution.tex file. The original TeX source remains
available below.
Solution Approach
For each connected component (tree), find its radius: the minimum over all nodes $v$ of the maximum distance from $v$ to any other node in the component. The node achieving this minimum is the center, and the radius is the eccentricity of the center.
The radius of a tree equals $\lceil \text{diameter} / 2 \rceil$ (where the center is the midpoint of the diameter path).
Sort the component radii in decreasing order: $r_1 \geq r_2 \geq \ldots \geq r_k$.
Connect the components optimally:
Connect all components through the component with the largest radius (star topology through it).
The resulting diameter is the maximum of:
The original diameter of any component.
$r_1 + r_2 + L$ (connecting the two largest-radius components).
$r_2 + r_3 + 2L$ (path through the center going through two edges of weight $L$).
itemize
The answer is: \[ \max\left(\max_i \text{diam}_i,\; r_1 + r_2 + L,\; r_2 + r_3 + 2L\right) \]
where $r_3 = 0$ if there are fewer than 3 components, and $r_2 = 0$ if there is only 1 component.
Complexity
Time: $O(N)$ for finding diameters and radii via BFS/DFS.
Space: $O(N)$
C++ Solution
#include <bits/stdc++.h> using namespace std; int travelTime(int N, int M, int L, int A[], int B[], int T[]){ vector<vector<pair<int,int>>> adj(N); for(int i = 0; i < M; i++){ adj[A[i]].push_back({B[i], T[i]}); adj[B[i]].push_back({A[i], T[i]}); } vector<bool> visited(N, false); vector<long long> dist(N, 0); // BFS/DFS to find farthest node from a given start auto bfs = [&](int start) -> pair<int, long long> { // Returns (farthest node, distance to it) queue<int> q; q.push(start); fill(dist.begin(), dist.end(), -1); dist[start] = 0; int farthest = start; long long maxDist = 0; while(!q.empty()){ int u = q.front(); q.pop(); for(auto [v, w] : adj[u]){ if(dist[v] == -1){ dist[v] = dist[u] + w; if(dist[v] > maxDist){ maxDist = dist[v]; farthest = v; } q.push(v); } } } return {farthest, maxDist}; }; // Find connected components, compute diameter and radius for each vector<long long> radii; long long maxDiam = 0; for(int s = 0; s < N; s++){ if(visited[s]) continue; // Find all nodes in this component // BFS from s auto [u, d1] = bfs(s); // u is farthest from s. BFS from u to find diameter endpoint. auto [v, diameter] = bfs(u); maxDiam = max(maxDiam, diameter); // Mark all visited // Also find radius: BFS from v, get dist from v. // The diameter path is u-v. Find center of this path. // Radius = ceil(diameter / 2) -- but with weighted edges, it's more nuanced. // Radius = min over all nodes x of max(dist_from_u[x], dist_from_v[x]) // dist currently holds distances from u. vector<long long> distU(N, -1); // Actually dist was set by bfs(u). Copy relevant values. // Let me redo: BFS from u, save distances. BFS from v, save distances. bfs(u); vector<long long> dU(N); for(int i = 0; i < N; i++) dU[i] = dist[i]; bfs(v); vector<long long> dV(N); for(int i = 0; i < N; i++) dV[i] = dist[i]; // Find radius: for each node in component, eccentricity = max(dU[x], dV[x]) // (since u and v are diameter endpoints, max distance from any node is // max(dist to u, dist to v)) // Radius = min eccentricity over all nodes in component. long long radius = LLONG_MAX; for(int x = 0; x < N; x++){ if(dU[x] >= 0 && dV[x] >= 0){ long long ecc = max(dU[x], dV[x]); radius = min(radius, ecc); visited[x] = true; } } radii.push_back(radius); } sort(radii.rbegin(), radii.rend()); // decreasing long long ans = maxDiam; if(radii.size() >= 2){ ans = max(ans, radii[0] + radii[1] + L); } if(radii.size() >= 3){ ans = max(ans, radii[1] + radii[2] + 2LL * L); } return (int)ans; } int main(){ int N, M, L; cin >> N >> M >> L; int *A = new int[M], *B = new int[M], *T = new int[M]; for(int i = 0; i < M; i++){ cin >> A[i] >> B[i] >> T[i]; } cout << travelTime(N, M, L, A, B, T) << "\n"; delete[] A; delete[] B; delete[] T; return 0; }
Code
Exact copied C++ implementation from solution.cpp.
#include <bits/stdc++.h>
using namespace std;
int travelTime(int N, int M, int L, int A[], int B[], int T[]){
vector<vector<pair<int,int>>> adj(N);
for(int i = 0; i < M; i++){
adj[A[i]].push_back({B[i], T[i]});
adj[B[i]].push_back({A[i], T[i]});
}
vector<bool> visited(N, false);
vector<long long> dist(N, 0);
// BFS/DFS to find farthest node from a given start
auto bfs = [&](int start) -> pair<int, long long> {
// Returns (farthest node, distance to it)
queue<int> q;
q.push(start);
fill(dist.begin(), dist.end(), -1);
dist[start] = 0;
int farthest = start;
long long maxDist = 0;
while(!q.empty()){
int u = q.front(); q.pop();
for(auto [v, w] : adj[u]){
if(dist[v] == -1){
dist[v] = dist[u] + w;
if(dist[v] > maxDist){
maxDist = dist[v];
farthest = v;
}
q.push(v);
}
}
}
return {farthest, maxDist};
};
// Find connected components, compute diameter and radius for each
vector<long long> radii;
long long maxDiam = 0;
for(int s = 0; s < N; s++){
if(visited[s]) continue;
// Find all nodes in this component
// BFS from s
auto [u, d1] = bfs(s);
// u is farthest from s. BFS from u to find diameter endpoint.
auto [v, diameter] = bfs(u);
maxDiam = max(maxDiam, diameter);
// Mark all visited
// Also find radius: BFS from v, get dist from v.
// The diameter path is u-v. Find center of this path.
// Radius = ceil(diameter / 2) -- but with weighted edges, it's more nuanced.
// Radius = min over all nodes x of max(dist_from_u[x], dist_from_v[x])
// dist currently holds distances from u.
vector<long long> distU(N, -1);
// Actually dist was set by bfs(u). Copy relevant values.
// Let me redo: BFS from u, save distances. BFS from v, save distances.
bfs(u);
vector<long long> dU(N);
for(int i = 0; i < N; i++) dU[i] = dist[i];
bfs(v);
vector<long long> dV(N);
for(int i = 0; i < N; i++) dV[i] = dist[i];
// Find radius: for each node in component, eccentricity = max(dU[x], dV[x])
// (since u and v are diameter endpoints, max distance from any node is
// max(dist to u, dist to v))
// Radius = min eccentricity over all nodes in component.
long long radius = LLONG_MAX;
for(int x = 0; x < N; x++){
if(dU[x] >= 0 && dV[x] >= 0){
long long ecc = max(dU[x], dV[x]);
radius = min(radius, ecc);
visited[x] = true;
}
}
radii.push_back(radius);
}
sort(radii.rbegin(), radii.rend()); // decreasing
long long ans = maxDiam;
if(radii.size() >= 2){
ans = max(ans, radii[0] + radii[1] + L);
}
if(radii.size() >= 3){
ans = max(ans, radii[1] + radii[2] + 2LL * L);
}
return (int)ans;
}
int main(){
int N, M, L;
cin >> N >> M >> L;
int *A = new int[M], *B = new int[M], *T = new int[M];
for(int i = 0; i < M; i++){
cin >> A[i] >> B[i] >> T[i];
}
cout << travelTime(N, M, L, A, B, T) << "\n";
delete[] A; delete[] B; delete[] T;
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[margin=1in]{geometry}
\usepackage{amsmath,amssymb,amsthm}
\usepackage{listings}
\usepackage{xcolor}
\usepackage{hyperref}
\lstset{
language=C++,
basicstyle=\ttfamily\small,
keywordstyle=\color{blue}\bfseries,
commentstyle=\color{green!60!black},
stringstyle=\color{red!70!black},
numbers=left,
numberstyle=\tiny\color{gray},
breaklines=true,
frame=single,
tabsize=4,
showstringspaces=false
}
\title{IOI 2013 -- Dreaming}
\author{Solution}
\date{}
\begin{document}
\maketitle
\section{Problem Summary}
Given a forest (collection of trees) with $N$ nodes and $M$ edges (each with a weight), you can add edges of weight $L$ to connect the trees into a single tree. Minimize the diameter (longest shortest path between any two nodes) of the resulting tree.
\section{Solution Approach}
\begin{enumerate}
\item For each connected component (tree), find its \textbf{radius}: the minimum over all nodes $v$ of the maximum distance from $v$ to any other node in the component. The node achieving this minimum is the \textbf{center}, and the radius is the eccentricity of the center.
The radius of a tree equals $\lceil \text{diameter} / 2 \rceil$ (where the center is the midpoint of the diameter path).
\item Sort the component radii in decreasing order: $r_1 \geq r_2 \geq \ldots \geq r_k$.
\item Connect the components optimally:
\begin{itemize}
\item Connect all components through the component with the largest radius (star topology through it).
\item The resulting diameter is the maximum of:
\begin{enumerate}
\item The original diameter of any component.
\item $r_1 + r_2 + L$ (connecting the two largest-radius components).
\item $r_2 + r_3 + 2L$ (path through the center going through two edges of weight $L$).
\end{enumerate}
\end{itemize}
\end{enumerate}
The answer is:
\[
\max\left(\max_i \text{diam}_i,\; r_1 + r_2 + L,\; r_2 + r_3 + 2L\right)
\]
where $r_3 = 0$ if there are fewer than 3 components, and $r_2 = 0$ if there is only 1 component.
\section{Complexity}
\begin{itemize}
\item \textbf{Time:} $O(N)$ for finding diameters and radii via BFS/DFS.
\item \textbf{Space:} $O(N)$
\end{itemize}
\section{C++ Solution}
\begin{lstlisting}
#include <bits/stdc++.h>
using namespace std;
int travelTime(int N, int M, int L, int A[], int B[], int T[]){
vector<vector<pair<int,int>>> adj(N);
for(int i = 0; i < M; i++){
adj[A[i]].push_back({B[i], T[i]});
adj[B[i]].push_back({A[i], T[i]});
}
vector<bool> visited(N, false);
vector<long long> dist(N, 0);
// BFS/DFS to find farthest node from a given start
auto bfs = [&](int start) -> pair<int, long long> {
// Returns (farthest node, distance to it)
queue<int> q;
q.push(start);
fill(dist.begin(), dist.end(), -1);
dist[start] = 0;
int farthest = start;
long long maxDist = 0;
while(!q.empty()){
int u = q.front(); q.pop();
for(auto [v, w] : adj[u]){
if(dist[v] == -1){
dist[v] = dist[u] + w;
if(dist[v] > maxDist){
maxDist = dist[v];
farthest = v;
}
q.push(v);
}
}
}
return {farthest, maxDist};
};
// Find connected components, compute diameter and radius for each
vector<long long> radii;
long long maxDiam = 0;
for(int s = 0; s < N; s++){
if(visited[s]) continue;
// Find all nodes in this component
// BFS from s
auto [u, d1] = bfs(s);
// u is farthest from s. BFS from u to find diameter endpoint.
auto [v, diameter] = bfs(u);
maxDiam = max(maxDiam, diameter);
// Mark all visited
// Also find radius: BFS from v, get dist from v.
// The diameter path is u-v. Find center of this path.
// Radius = ceil(diameter / 2) -- but with weighted edges, it's more nuanced.
// Radius = min over all nodes x of max(dist_from_u[x], dist_from_v[x])
// dist currently holds distances from u.
vector<long long> distU(N, -1);
// Actually dist was set by bfs(u). Copy relevant values.
// Let me redo: BFS from u, save distances. BFS from v, save distances.
bfs(u);
vector<long long> dU(N);
for(int i = 0; i < N; i++) dU[i] = dist[i];
bfs(v);
vector<long long> dV(N);
for(int i = 0; i < N; i++) dV[i] = dist[i];
// Find radius: for each node in component, eccentricity = max(dU[x], dV[x])
// (since u and v are diameter endpoints, max distance from any node is
// max(dist to u, dist to v))
// Radius = min eccentricity over all nodes in component.
long long radius = LLONG_MAX;
for(int x = 0; x < N; x++){
if(dU[x] >= 0 && dV[x] >= 0){
long long ecc = max(dU[x], dV[x]);
radius = min(radius, ecc);
visited[x] = true;
}
}
radii.push_back(radius);
}
sort(radii.rbegin(), radii.rend()); // decreasing
long long ans = maxDiam;
if(radii.size() >= 2){
ans = max(ans, radii[0] + radii[1] + L);
}
if(radii.size() >= 3){
ans = max(ans, radii[1] + radii[2] + 2LL * L);
}
return (int)ans;
}
int main(){
int N, M, L;
cin >> N >> M >> L;
int *A = new int[M], *B = new int[M], *T = new int[M];
for(int i = 0; i < M; i++){
cin >> A[i] >> B[i] >> T[i];
}
cout << travelTime(N, M, L, A, B, T) << "\n";
delete[] A; delete[] B; delete[] T;
return 0;
}
\end{lstlisting}
\end{document}
#include <bits/stdc++.h>
using namespace std;
int travelTime(int N, int M, int L, int A[], int B[], int T[]){
vector<vector<pair<int,int>>> adj(N);
for(int i = 0; i < M; i++){
adj[A[i]].push_back({B[i], T[i]});
adj[B[i]].push_back({A[i], T[i]});
}
vector<bool> visited(N, false);
vector<long long> dist(N, 0);
// BFS/DFS to find farthest node from a given start
auto bfs = [&](int start) -> pair<int, long long> {
// Returns (farthest node, distance to it)
queue<int> q;
q.push(start);
fill(dist.begin(), dist.end(), -1);
dist[start] = 0;
int farthest = start;
long long maxDist = 0;
while(!q.empty()){
int u = q.front(); q.pop();
for(auto [v, w] : adj[u]){
if(dist[v] == -1){
dist[v] = dist[u] + w;
if(dist[v] > maxDist){
maxDist = dist[v];
farthest = v;
}
q.push(v);
}
}
}
return {farthest, maxDist};
};
// Find connected components, compute diameter and radius for each
vector<long long> radii;
long long maxDiam = 0;
for(int s = 0; s < N; s++){
if(visited[s]) continue;
// Find all nodes in this component
// BFS from s
auto [u, d1] = bfs(s);
// u is farthest from s. BFS from u to find diameter endpoint.
auto [v, diameter] = bfs(u);
maxDiam = max(maxDiam, diameter);
// Mark all visited
// Also find radius: BFS from v, get dist from v.
// The diameter path is u-v. Find center of this path.
// Radius = ceil(diameter / 2) -- but with weighted edges, it's more nuanced.
// Radius = min over all nodes x of max(dist_from_u[x], dist_from_v[x])
// dist currently holds distances from u.
vector<long long> distU(N, -1);
// Actually dist was set by bfs(u). Copy relevant values.
// Let me redo: BFS from u, save distances. BFS from v, save distances.
bfs(u);
vector<long long> dU(N);
for(int i = 0; i < N; i++) dU[i] = dist[i];
bfs(v);
vector<long long> dV(N);
for(int i = 0; i < N; i++) dV[i] = dist[i];
// Find radius: for each node in component, eccentricity = max(dU[x], dV[x])
// (since u and v are diameter endpoints, max distance from any node is
// max(dist to u, dist to v))
// Radius = min eccentricity over all nodes in component.
long long radius = LLONG_MAX;
for(int x = 0; x < N; x++){
if(dU[x] >= 0 && dV[x] >= 0){
long long ecc = max(dU[x], dV[x]);
radius = min(radius, ecc);
visited[x] = true;
}
}
radii.push_back(radius);
}
sort(radii.rbegin(), radii.rend()); // decreasing
long long ans = maxDiam;
if(radii.size() >= 2){
ans = max(ans, radii[0] + radii[1] + L);
}
if(radii.size() >= 3){
ans = max(ans, radii[1] + radii[2] + 2LL * L);
}
return (int)ans;
}
int main(){
int N, M, L;
cin >> N >> M >> L;
int *A = new int[M], *B = new int[M], *T = new int[M];
for(int i = 0; i < M; i++){
cin >> A[i] >> B[i] >> T[i];
}
cout << travelTime(N, M, L, A, B, T) << "\n";
delete[] A; delete[] B; delete[] T;
return 0;
}