前言
难的不是网络流,难的是想到这么做,难的是证明
题目
Atcoder
大意
给你一个数
n
n
n 和
n
−
1
n-1
n−1 个集合
E
i
E_i
Ei ,其中
E
i
∈
{
1
,
2
,
.
.
.
,
n
}
E_i\in\{1,2,...,n\}
Ei∈{1,2,...,n}
现在从每个集合
E
i
E_i
Ei 中选出一对数
(
u
i
,
v
i
)
(u_i,v_i)
(ui,vi) 组成一条边,使得选出
n
−
1
n-1
n−1 条边构成一颗树,输出选择方案,没有输出
−
1
-1
−1
2
≤
n
≤
1
0
5
2\le n \le10^5
2≤n≤105
∣
E
i
∣
≥
2
,
∑
∣
E
i
∣
≤
2
∗
1
0
5
|E_i|\ge2,\sum|E_i|\le2*10^5
∣Ei∣≥2,∑∣Ei∣≤2∗105
思路
先将做法再说证明,因为并不知道怎么想出这一做法的…
做法
考虑以
1
1
1 为根,那么每条边可以表示成
(
f
a
u
,
u
)
(fa_u,u)
(fau,u),先不考虑
f
a
u
fa_u
fau 只考虑
u
u
u 的选择,那么
n
−
1
n-1
n−1 个集合对应
n
−
1
n-1
n−1 个数,跑最大匹配,如果匹配数
<
n
−
1
<n-1
<n−1 就无解
设
E
i
E_i
Ei 匹配
x
i
x_i
xi
接下来构造每个数
f
a
u
fa_u
fau,采用从
1
1
1 出发的
B
F
S
BFS
BFS,设当前已经变遍历点集合为
S
S
S 对于
E
i
E_i
Ei 若
E
i
∩
S
≠
ϕ
E_i\cap S\not=\phi
Ei∩S=ϕ 则将
x
i
x_i
xi 加入
S
S
S 不断更新即可,若最后无法遍历
n
n
n 个点,输出无解
证明
考虑如果出现第二种无解情况,即出现分层的情况,设分层时遍历 E i E_i Ei 的集合为 S S S,未遍历的 E i E_i Ei 的集合为 T T T ,现在尝试更换匹配方式看是否可能有解:
-
S
S
S 和
T
T
T 中交换
发现这是不可能的,因为假设对两个 E 1 , E 2 E_1,E_2 E1,E2 交换两个编号为 x 1 , x 2 x_1,x_2 x1,x2 的,那么发现交换前后 E 1 , E 2 E_1,E_2 E1,E2 都是连通的,矛盾 -
S
S
S 和
S
S
S 中交换
并不会影响 T T T,还是分层 -
T
T
T 和
T
T
T 中交换
并不会影响 S S S,还是分层
Besides
注意用 D i n i c Dinic Dinic 跑最大匹配是 m n m\sqrt{n} mn 的
代码
#include<map>
#include<set>
#include<stack>
#include<queue>
#include<cmath>
#include<cstring>
#include<climits>
#include<cstdio>
#include<iostream>
#include<algorithm>
using namespace std;
#define LL long long
//#define getchar() (p1 == p2 && (p2 = (p1 = buf) + fread(buf, 1, 1 << 21, stdin), p1 == p2) ? EOF : *p1++)
//char buf[(1 << 21) + 1], *p1 = buf, *p2 = buf;
inline int read() {
bool f=0;int x=0;char c=getchar();
while(c<'0'||'9'<c){if(c==EOF)exit(0);if(c=='-')f=1;c=getchar();}
while('0'<=c&&c<='9') x=(x<<3)+(x<<1)+(c^48),c=getchar();
return !f?x:-x;
}
#define MAXN 200000
#define MAXM 500000
#define INF 0x3f3f3f3f
struct Edge{
int nxt,v,cap;
}edge[2*MAXM+5];
int ecnt,head[MAXN+5],cur[MAXN+5];
void Init(){
ecnt=-1,memset(head,-1,sizeof(head));
return ;
}
void Addedge(int u,int v,int cap){
//printf("%d %d %d\n",u,v,cap);
edge[++ecnt]=(Edge){head[u],v,cap},head[u]=ecnt;
edge[++ecnt]=(Edge){head[v],u,0},head[v]=ecnt;
return ;
}
int N,S,T;
int dep[MAXN+5];
bool BFS(){
queue<int> Q;
for(int i=0;i<=N;i++)
dep[i]=INF;
dep[S]=0,Q.push(S);
while(!Q.empty()){
int u=Q.front();Q.pop();
for(int i=head[u];~i;i=edge[i].nxt){
int v=edge[i].v,cap=edge[i].cap;
if(dep[v]==INF&&cap)
dep[v]=dep[u]+1,Q.push(v);
}
}
return dep[T]<INF;
}
int DFS(int u,int aug){
if(u==T) return aug;
int flow=0,f;
for(int &i=cur[u];~i;i=edge[i].nxt){
int v=edge[i].v,cap=edge[i].cap;
if(dep[v]==dep[u]+1&&cap&&(f=DFS(v,min(aug,cap)))){
aug-=f,flow+=f;
edge[i].cap-=f,edge[i^1].cap+=f;
if(!aug) break;
}
}
return flow;
}
int Dinic(){
int Max_Flow=0;
while(BFS())
memcpy(cur,head,sizeof(head)),Max_Flow+=DFS(S,INF);
return Max_Flow;
}
queue<int> Q;
int ma[MAXN+5];
bool vis[MAXN+5];
vector<int> G[MAXN+5];
int cho1[MAXN+5],cho2[MAXN+5];
int main(){
Init();
int n=read();
N=2*n+2,S=2*n+1,T=2*n+2;
for(int i=1;i<n;i++)
Addedge(S,i,1);
for(int i=2;i<=n;i++)
Addedge(i+n-1,T,1);
for(int i=1;i<n;i++){
int c=read();
for(int j=1;j<=c;j++){
int w=read();
G[w].push_back(i);
if(w!=1)
Addedge(i,w+n-1,1);
}
}
int ans=Dinic();
if(ans!=n-1)
puts("-1"),exit(0);
for(int s=1;s<n;s++)
for(int i=head[s];~i;i=edge[i].nxt)
if(!edge[i].cap)
ma[s]=edge[i].v-(n-1);
Q.push(1);
int cnt=0;
while(!Q.empty()){
cnt++;
int u=Q.front();Q.pop();
for(int i=0;i<(int)G[u].size();i++){
int s=G[u][i];
if(vis[s]) continue;
vis[s]=1,Q.push(ma[s]);
cho1[s]=u,cho2[s]=ma[s];
}
}
if(cnt!=n)
puts("-1"),exit(0);
for(int i=1;i<n;i++)
printf("%d %d\n",cho1[i],cho2[i]);
return 0;
}
感悟
给我最大启发是 集合对于边的两个点选择确定根后转化为对一个点的选择+妙妙的证明转化成最大匹配问题