前言
难的不是网络流,难的是想到这么做,难的是证明
题目
Atcoder
大意
给你一个数
和
个集合
,其中
现在从每个集合
中选出一对数
组成一条边,使得选出
条边构成一颗树,输出选择方案,没有输出
思路
先将做法再说证明,因为并不知道怎么想出这一做法的…
做法
考虑以
为根,那么每条边可以表示成
,先不考虑
只考虑
的选择,那么
个集合对应
个数,跑最大匹配,如果匹配数
就无解
设
匹配
接下来构造每个数
,采用从
出发的
,设当前已经变遍历点集合为
对于
若
则将
加入
不断更新即可,若最后无法遍历
个点,输出无解
证明
考虑如果出现第二种无解情况,即出现分层的情况,设分层时遍历 的集合为 ,未遍历的 的集合为 ,现在尝试更换匹配方式看是否可能有解:
-
和
中交换
发现这是不可能的,因为假设对两个 交换两个编号为 的,那么发现交换前后 都是连通的,矛盾 -
和
中交换
并不会影响 ,还是分层 -
和
中交换
并不会影响 ,还是分层
Besides
注意用 跑最大匹配是 的
代码
#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;
}
感悟
给我最大启发是 集合对于边的两个点选择确定根后转化为对一个点的选择+妙妙的证明转化成最大匹配问题