Construction of a tree

AGC029F - Construction of a tree

Problem

给定 \(N\) 个元素的集合 \(\{1,2,\ldots,N\}\)\(N-1\) 个子集。第 \(i\) 个集合记为 \(E_i\)

你需要从每个集合 \(E_i\) 中选择两个不同的元素 \(u_i, v_i\),以 \(\{1,2,\ldots,N\}\) 作为顶点集,\((u_1,v_1),(u_2,v_2),\ldots,(u_{N-1},v_{N-1})\) 作为边集,构造一个 \(N\) 个顶点、\(N-1\) 条边的图 \(T\)。请判断是否可以通过合理选择 \(u_i, v_i\),使得 \(T\) 是一棵树。如果可以,请给出一组实际的 \((u_1,v_1),(u_2,v_2),\ldots,(u_{N-1},v_{N-1})\) 使得 \(T\) 是一棵树。

Thinking

不是很好找思路,选取问题不是很好贪心,考虑网络流之类的问题

乱搞

先铺垫一个赛时乱搞做法,可以AT上能获得 AC34 WA 39 的好成绩

\(s \to 子集\), \(子集 \to 属于子集的每个点\)\(每个点 \to t\)

然后发现这样的建图是没有办法区分跨子集的连边的,会有点集重复连边

然后考虑用并查集维护连通性

暴力枚举重复的点集,直接找到可连的就连

显然正确性是不保证的

赛时代码

#include <bits/stdc++.h>
using namespace std;
const int N = 3e5 + 10, inf = 0x3f3f3f3f;
int n, s, t, lev[N], siz = 0, lst[N], col[N], p[N][2];
struct edge {int to, rev, val; };
vector <edge> e[N];
void ade(int u, int v, int val) {e[u].emplace_back(edge{v, e[v].size(), val}), e[v].emplace_back(edge{u, e[u].size() - 1, 0});}
bool bfs() {
	memset(lev, 0, sizeof(int) * (siz + 1));
	queue <int> q; lev[s] = 1; q.emplace(s);
	while (q.size()) {
		int u = q.front(); q.pop();
		for (auto now : e[u]) {
			int v = now.to, rev = now.rev, val = now.val;
//			cout << v << ' ' << rev << ' ' << val << '\n';
			if (val && !lev[v]) lev[v] = lev[u] + 1, q.emplace(v);
		}
	}
	return lev[t];
}
int dfs(int u, int flow) {
	if (u == t) return flow;
	int res = 0;
	for (int i = lst[u]; i < e[u].size() && flow; i++) {
		lst[u] = i; edge now = e[u][i]; int v = now.to, rev = now.rev, val = now.val;
		if (val && lev[v] == lev[u] + 1) {
			int d = dfs(v, min(flow, val));
			if (d) e[u][i].val -= d, e[v][rev].val += d, flow -= d, res += d;
			else lev[v] = 0;
		}
	}
	return res;
} 
int dinic() {
	int ret = 0;
	while(bfs()) {
		memset(lst, 0, sizeof(int) * (siz + 1));
		while (int f = dfs(s, inf)) ret += f;
	}
	return ret;
}
vector <int> bck[N];
int fa[N];
int find(int u) {return fa[u] == u ? u : fa[u] = find(fa[u]);}
vector <int> l;

struct buff {
	vector <int> a[N];
	vector <pair <int, int>> ans;
	int fa[15];
	int find(int u) {return fa[u] == u ? u : fa[u] = find(fa[u]);}
	void dfs(int k) {
		{
			for (int i = 1; i <= 10; i++) fa[i] = i;
			for (auto x : ans) {
				int u = x.first, v = x.second;
				if (find(u) != find(v)) fa[find(u)] = find(v); else return;
			} 
		}
		if (k == n) {
			for (auto x : ans) cout << x.first << ' ' << x.second << '\n';
			exit(0);
		}
//		cout << "-----\n";
//			for (auto x : ans) cout << x.first << ' ' << x.second << '\n';
			int s = a[k].size();
			for (int i = 0; i < s; i++)
				for (int j = i + 1; j < s; j++) {
					ans.emplace_back(a[k][i], a[k][j]);
//					cout << "?" << k << ' ' << a[k][i] << ' ' << a[k][j] << '\n';
					dfs(k + 1);
					ans.pop_back();
				}
	}
	void solve() {
		
		for (int k = 1; k < n; k++) {
			int c; cin >> c;
			for (int u, i = 1; i <= c; i++) cin >> u, a[k].emplace_back(u);
		}
		dfs(1);
		cout << -1 << '\n';
	}
}bf;
int main() {
	cin.tie(nullptr) -> ios::sync_with_stdio(0);
	cin >> n;
	if (n <= 10) {bf.solve(); return 0;}
	s = n * 2 + 1, t = s + 1, siz = t;
	for (int i = 1; i < n; i++) ade(s, n + i, 2);
	for (int c, i = 1; i < n; i++) {
		cin >> c;
		for (int u, j = 0; j < c; j++) cin >> u, ade(n + i, u, 1), bck[i].emplace_back(u);
	}
	for (int i = 1; i <= n; i++) ade(i, t, 2);
	int d = dinic();
	if (d < n) return cout << -1, 0;
	for (int i = 1; i <= n; i++) fa[i] = i;
	for (int i = 1; i < n; i++) {
		int u = i + n;
		int tot = 0;
		for (auto now : e[u]) {
			int v = now.to, rev = now.rev, val = now.val;
			if (!val) p[i][tot++] = v;
		}
		int f1 = find(p[i][0]), f2 = find(p[i][1]);
		if (f1 != f2) fa[f1] = f2;
		else l.emplace_back(i);
	}
//	for (int i = 1; i < n; i++) cout << p[i][0] << ' ' << p[i][1] << '\n';
	for (auto k : l) {
		int s = bck[k].size();
		for (int i = 0; i < s; i++) 
			for (int j = i + 1; j < s; j++) {
				if (find(bck[k][i]) != find(bck[k][j])) {
//					cout << bck[k][i] << ' ' << bck[k][j] << '\n';
					p[k][0] = bck[k][i], p[k][1] = bck[k][j];
					fa[find(bck[k][i])] = find(bck[k][j]);
					break;
				}
			}
	}
	int rt = find(1);
	for (int i = 1; i <= n; i++) if (find(i) != rt) return cout << -1, 0;
	for (int i = 1; i < n; i++) cout << p[i][0] << ' ' << p[i][1] << '\n';
	return 0;
} 

Solution

转化为二分图

考虑固定 \(1\) 为树的根。
在最终的树中,每条边可以看作从父节点指向子节点。
对于每个非根节点 \(u \in \{2,3,\dots,n\}\),它恰好有一个父节点,且该父节点必须与 \(u\) 同时出现在某个子集 \(E_i\) 中。
反过来,每个子集 \(E_i\)恰好产生一条边,即它需要确定一个父节点和一个子节点。

这提示我们可以建立如下的二分图:

  • 左部:所有非根节点 \(2,3,\dots,n\)(共 \(n-1\) 个点)。
  • 右部:所有子集 \(1,2,\dots,n-1\)(也是 \(n-1\) 个点)。
  • 如果点 \(u\) 属于子集 \(i\),则在左部点 \(u\) 与右部点 \(i\) 之间连一条边。

然后我们在二分图上求完备匹配(左部所有点都被匹配,右部所有点也都被匹配)。
匹配的含义:

对于每个子集 \(i\),匹配到的左部点 \(u\) 表示“子集 \(i\) 所连的边中,子节点是 \(u\)”。

因为左部点恰好 \(n-1\) 个,右部也 \(n-1\) 个,完美匹配如果存在,则每个子集都分配了一个唯一的子节点。

建图

使用网络流求二分图匹配:

  • 源点 \(S\) 向每个左部点(节点 \(2 \sim n\))连容量为 \(1\) 的边。
  • 每个右部点(子集 \(i\))向汇点 \(T\) 连容量为 \(1\) 的边。
  • 若节点 \(u\)\(u \ge 2\))属于子集 \(i\),则从 \(u\)\(i\) 连容量为 \(1\) 的边。

跑最大流,若最大流量 \(< n-1\),则无解,输出 -1

BFS构造

记匹配的结果为:

  • 对于每个子集 \(i\),设 match[i] 为它所匹配到的左部点(即子节点)。

现在我们需要确定每条边的父节点
我们从根 \(1\) 开始 BFS:

  • 初始时,根节点 \(1\) 已确定,将其加入队列。
  • 维护两个布尔数组:
    • vis[x]:节点 \(x\) 是否已经被加入树中(即已确定父节点)。
    • used[i]:子集 \(i\) 是否已经被用来构造边。
  • 对于当前出队的节点 \(x\),遍历所有包含 \(x\) 的子集 \(i\)(即 \(x \in E_i\)):
    • 如果 \(i\) 还没有被使用,那么子集 \(i\) 的边就应该以 \(x\) 作为父节点,以 match[i] 作为子节点。
    • 记录答案 ans[i] = x,标记 used[i] = true
    • 如果子节点 match[i] 尚未被访问(!vis[match[i]]),则标记它并加入队列。

如果 BFS 结束时所有节点都被访问(即 vis[1..n] 全为真),则得到了合法的树,输出所有 (ans[i], match[i]);否则输出 -1

复杂度

  • 点数 \(O(n)\),边数 \(O(\sum |E_i|)\)
  • Dinic 在单位容量二分图上的复杂度为 \(O(\sqrt{n} \cdot m)\),其中 \(m = \sum |E_i| + O(n)\)
  • 总时间复杂度 \(O((n+\sum|E_i|)\sqrt{n})\),空间复杂度 \(O(n+\sum|E_i|)\),可以通过 \(n \le 10^5\) 的数据。

参考代码(AC)

#include <bits/stdc++.h>
#define cpl int v = now.to, rev = now.rev, val = now.val
using namespace std;
const int N = 3e5 + 10, inf = 0x3f3f3f3f;
int n, s, t, lev[N], siz = 0, lst[N], match[N], ans[N];
struct edge {int to, rev, val; };
vector <edge> e[N]; vector <int> pos[N];
queue <int> q;
bool used[N], vis[N];
void ade(int u, int v, int val) {e[u].emplace_back(edge{v, (int)e[v].size(), val}), e[v].emplace_back(edge{u, (int)e[u].size() - 1, 0});}
bool bfs() {
	memset(lev, 0, sizeof(int) * (siz + 1));
	queue <int> q; lev[s] = 1; q.emplace(s);
	while (q.size()) {
		int u = q.front(); q.pop();
		for (auto now : e[u]) {
			cpl;
			if (val && !lev[v]) lev[v] = lev[u] + 1, q.emplace(v);
		}
	}
	return lev[t];
}
int dfs(int u, int flow) {
	if (u == t) return flow;
	int res = 0;
	for (int i = lst[u]; i < (int)e[u].size() && flow; i++) {
		lst[u] = i; edge now = e[u][i]; cpl;
		if (val && lev[v] == lev[u] + 1) {
			int d = dfs(v, min(flow, val));
			if (d) e[u][i].val -= d, e[v][rev].val += d, flow -= d, res += d;
			else lev[v] = 0;
		}
	}
	return res;
} 
int dinic() {
	int ret = 0;
	while(bfs()) {
		memset(lst, 0, sizeof(int) * (siz + 1));
		while (int f = dfs(s, inf)) ret += f;
	}
	return ret;
}
int main() {
	cin.tie(nullptr) -> ios::sync_with_stdio(0);
	cin >> n;
	s = n * 2 + 1, t = s + 1, siz = t;
	for (int i = 1; i < n; i++) ade(s, n + i, 1);
	for (int i = 2; i <= n; i++) ade(i, t, 1);
	for (int i = 1; i < n; i++) {
		int c; cin >> c;
		for (int u, j = 0; j < c; j++) {
			cin >> u;
			if (u != 1) ade(n + i, u, 1);
			pos[u].push_back(i);
		}
	}
	int d = dinic();
	if (d != n - 1) return cout << "-1", 0;
	for (int i = 1; i < n; i++) {
		int u = n + i;
		for (auto now : e[u]) {
			cpl;
			if (v >= 2 && v <= n && val == 0) {match[i] = v; break;}
		}
	}
	vis[1] = 1; q.emplace(1);
	while (q.size()) {
		int x = q.front(); q.pop();
		for (auto i : pos[x]) {
			if (!used[i]) {
				int p = match[i]; ans[i] = x, used[i] = 1;
				if (!vis[p]) vis[p] = 1, q.push(p);
			}
		}
	}
	for (int i = 1; i <= n; i++) if (!vis[i]) return cout << "-1", 0;
	for (int i = 1; i < n; i++) cout << ans[i] << ' ' << match[i] << '\n';
	return 0;
}
posted @ 2026-04-11 16:54  Aojun  阅读(4)  评论(0)    收藏  举报