题解【CF847J Students Initiation】
传送门。
$\texttt{Description}$
有 $n$ 个人,$m$ 对关系,要求每对关系中,有且仅有一个人给另外一个人送礼物,并且使送出礼物最多的人送的礼物尽可能少。并输出送礼物的方案。
$1\le n,m\le 5\times 10^3$。
$\texttt{Solution}$
这翻译全都贺出去了,全都贺出去了啊
仍然考虑构造一个二分图。
一开始想了一个非常 $\texttt{naive}$ 的做法,左边是送礼物的人 $i$,右边对应是收礼物的人 $i'$。
对于一个关系 $(u,v)$,如果建边方式为 $u\to v'$ 和 $v\to u'$,且容量都为 $1$,则【有且仅有一个人给另外一个人送礼物】这个条件是无法表示出来的。
考虑左边还是送礼物的人,右边变成方案。
考虑将一个方案表示成一个点,对于第 $i$ 个方案,我们连边 $u_i\to i$,$v_i\to i$,容量均为 $1$,然后对于每一个方案与 $t$ 连一条容量为 $1$ 的边,这样就保证两个送出礼物最终只有一个能流到 $t$。
那么对于每一个人 $i$,$s$ 和 $i$ 之间如何连边呢?
看到了【最大值尽可能小】限制,很容易想到二分,我们二分每一个最多送出的礼物数量 $k$,所以对于每一个 $i$,连一条 $s\to i$,容量为 $k$ 的边。
最后输出只需要找那些残余流量为 $0$ 的边就行了。
还有一个大坑点,调了好久:每一次跑 $\text{dinic}$ 都是需要重新建图的,所以就导致,找到正确答案后,还会重新建一些新的图,这就导致找残余容量为 $0$ 的边会找错。
所以只需要找完答案后,重新建一遍图跑一个 $\text{dinic}$ 即可。
代码:
inline bool check(int k) { remake(); rep(i,1,m) { add_edge(nx[i],i+n,1); add_edge(ny[i],i+n,1); } rep(i,1,n) add_edge(s,i,k); rep(i,1,m) add_edge(i+n,t,1); int ans(0); while(bfs()) ans+=dfs(s,INF); return ans==m&&ans; } int main() { n=read(),m=read(); rep(i,1,m) nx[i]=read(),ny[i]=read(); int l=1,r=n,res(0); s=0,t=n+m+1; while(l<=r) { int mid=(l+r)>>1; if(check(mid)) r=mid-1,res=mid; else l=mid+1; } printf("%d\n",res); check(res); rep(i,1,m) { bool flag(false); gra(j,nx[i]) if(edge[j].to==i+n&&!edge[j].flow) { printf("%d %d\n",nx[i],ny[i]); flag=true; break; } if(!flag) printf("%d %d\n",ny[i],nx[i]); } return 0; }
$$\texttt{The End.by UF}$$