Description
给定 n − 1 n-1 n−1 个点集 E 1 , E 2 , ⋯ , E n − 1 E_1,E_2,\cdots,E_{n-1} E1,E2,⋯,En−1,请从每个点集中选两个点连边,构成一棵树。
若不存在这样的方案,请输出 − 1 -1 −1;否则请输出方案。
1 ≤ N ≤ 1 0 5 , ∑ i = 1 n − 1 ∣ E i ∣ ≤ 2 × 1 0 5 1 \le N \le 10^5,\sum_{i=1}^{n-1}{|E_i|} \le 2 \times 10^5 1≤N≤105,∑i=1n−1∣Ei∣≤2×105
Solution
首先,形成一棵树的必要条件是: 所有点的入度非 0 0 0。
考虑先满足这个条件。我们建立一个二分图,左边有 n − 1 n-1 n−1 个点,右边有 n n n 个点。对于每一个点集 E i E_i Ei,我们将 i i i 向 E i E_i Ei 中所有的点连边。若此二分图的最大匹配为 n − 1 n-1 n−1,则我们就满足了上述条件。
我们可以通过 Dinic 在 O ( n n ) O(n \sqrt n) O(nn) 的代价内求出最大匹配与所有在最大匹配中的边。
为了方便叙述,令左侧点 i i i 与 v i v_i vi 匹配。我们对每个左侧点 i i i,钦定第 i i i 条边的一端为 v i v_i vi,另一端为 u i u_i ui。
现在关键在于构造出所有的 u i u_i ui。
注意到右侧有且仅有一个点失配,考虑以此切入。令这失配的点为
t
t
t。我们先将
p
p
p 赋值为
t
t
t,然后执行下列步骤:
①遍历与
p
p
p 相邻且未被访问过的左侧点
q
q
q;
②令
q
q
q 被访问过;
③令第
p
p
p 行的一组答案为
(
u
p
,
v
q
)
(u_p,v_q)
(up,vq);
④将
u
u
u 赋值为
v
q
v_q
vq;
⑤从①重新开始。
特别的,当在 ① 中无法找到一个合法的 q q q 时,直接宣布无解即可。
由于 Dinic 求二分图最大匹配问题的复杂度为 O ( m n ) O(m \sqrt n) O(mn),而本题 n , m n,m n,m 同级,从而总复杂度 O ( n n ) O(n \sqrt n) O(nn)。
下面给出关于正确性的简要证明。
Lemma 1
得到的答案一定是一组合法解。
Prove
不难发现,只要每行都挑选出了两个点 ( u , v ) (u,v) (u,v) 并将其相连,且最终得到的图没有环,则它一定是我们所需要的树。
考虑在构造出来的树中是否会出现环。
我们运用反证法。令存在环 a 1 → a 2 → ⋯ → a k → a 1 a_1\to a_2 \to \cdots \to a_k \to a_1 a1→a2→⋯→ak→a1。
- 若
a
1
≠
t
a_1 \neq t
a1=t
- 不难发现,在搜索到 a k a_k ak 之前,与 a 1 , a 2 , ⋯ , a k a_1,a_2,\cdots,a_k a1,a2,⋯,ak 匹配的左侧点都被标记为“已访问”,从而不可能从 a k a_k ak 搜到 a 1 a_1 a1 并形成环。
- 若
a
1
=
t
a_1=t
a1=t
- 由于 t t t 没有匹配的点,所以离开 t t t 之后就不可能再回来了,从而不可能形成环。
证毕。
Lemma 2
不可能将 ⌊ \lfloor ⌊ 有解 ⌉ \rceil ⌉ 判断为 ⌊ \lfloor ⌊ 无解 ⌉ \rceil ⌉。
Prove
无解共有两种情况。
第一种情况是在做二分图匹配时发现最大匹配不为 n − 1 n-1 n−1。根据文章开头提出的“引理”,此时一定无解。
第二种情况是在构造解时发现无法找到满足要求的 q q q。首先,我们可以发现,此时未经过的左侧点与右侧点数量相等,无论接下来如何处理均会出现环。同时,对于此时任意一个左侧未被经过的点 r r r,与 r r r 相邻的右侧节点均未经过,从而无法进行调整。由于无法继续进行且无法调整,我们无力回天。
证毕。
综上所述,得到的答案一定合法,且不会将有解判断为无解。我们解法的正确性得到了证明。
Code
#include <bits/stdc++.h>
using namespace std;
const int maxl=200005;
int read(){
int s=0,w=1;char ch=getchar();
while (ch<'0'||ch>'9'){if (ch=='-') w=-w;ch=getchar();}
while (ch>='0'&&ch<='9'){s=(s<<1)+(s<<3)+(ch^'0');ch=getchar();}
return s*w;
}
int n,s,t,res,cnt=1,cur,pos,flag;
int head[maxl],mat[maxl],vis[maxl],dep[maxl];
struct Tree{int u,v;}ans[maxl];
struct edge{int nxt,to,f;}e[maxl<<2];
void add_edge(int u,int v,int flow){
cnt++;
e[cnt].to=v,e[cnt].f=flow,e[cnt].nxt=head[u],head[u]=cnt;
}
void Add(int u,int v,int f){add_edge(u,v,f),add_edge(v,u,0);}
bool bfs(){
for (int i=s;i<=t;i++) dep[i]=0;
queue<int> q;
q.push(s),dep[s]=1;
while (!q.empty()){
int now=q.front();q.pop();
for (int i=head[now];i;i=e[i].nxt){
int y=e[i].to;
if (e[i].f && (!dep[y])){
dep[y]=dep[now]+1;
q.push(y);
}
}
}
return dep[t];
}
int dfs(int now,int flow){
if (now==t) return flow;
int Out=0;
for (int i=head[now];i;i=e[i].nxt){
if (flow<=0) break;
int y=e[i].to;
if (dep[y]==dep[now]+1&&e[i].f){
int tmp=dfs(y,min(flow,e[i].f));
e[i].f-=tmp,e[i^1].f+=tmp;
flow-=tmp,Out+=tmp;
}
}
if (!Out) dep[now]=-114514;
return Out;
}
void dfs2(int now){
for (int i=head[now];i;i=e[i].nxt){
int y=e[i].to;
if (y>n||y==s||vis[y]) continue;
vis[y]=1,cnt++;
ans[y].u=now-n,ans[y].v=mat[y]-n;
dfs2(mat[y]);
}
}
signed main(){
n=read(),s=0,t=2*n+1;
for (int i=1;i<=n;i++) Add(i+n,t,1);
for (int i=1;i<n;i++){
int x=read();
Add(s,i,1);
for (int j=1;j<=x;j++){
int y=read();
Add(i,y+n,1);
}
}
while (bfs()) res+=dfs(s,1e9);
if (res<n-1) return cout<<-1<<endl,0;
for (int now=1;now<n;now++){
for (int i=head[now];i;i=e[i].nxt){
int y=e[i].to;
if (!e[i].f&&y>n) mat[now]=y,vis[y]=1;
}
}
for (int i=1;i<=n;i++){
if (!vis[i+n]){
cur=i;
break;
}
}
memset(vis,0,sizeof(vis)),cnt=0;
dfs2(cur+n);
if (cnt==n-1){
for (int i=1;i<n;i++) printf("%d %d\n",ans[i].u,ans[i].v);
}
else puts("-1");
return 0;
}
应该讲得很清楚了吧 ⋯ ⋯ \cdots \cdots ⋯⋯