网络流 和 图论相关概念
关于二分图的一些结论
最小点覆盖 \(=\) 最大匹配数(König定理)
最大独立集 \(=\) \(n-\) 最大匹配数
最小边覆盖 \(=\) \(n-\)最大匹配数
覆盖集:
简记:点覆盖边,边覆盖点。
-
最小点覆盖:选择最少的点,使得任意一条边都至少有一个端点在选择的点集中。
-
最小边覆盖:选择最少的边,使得任意一个点都至少为选择的一条边的端点。
-
最小路径覆盖:选择最少的路径,路径不能有点相交,覆盖所有点。
求解方法:
建一张二分图,对于每个点拆点,拆成左部点 \(u\) 和右部点 \(u\prime\),对于原图上一条边 \(u->v\),在新图上连边 \(u->v\prime\),然后使用网络流,答案为 \(n-\) 最大匹配数。
证明:设最初每一个点都为一条路径,我们将二分图上两个点匹配,就相当于将这两个点原来所属的路径首尾相连,因为一条路径的一端最多和一条路径相接,所以求的就是匹配。
注意:如果图中存在孤点,算出来的答案会将孤点视为一条路径。
该方法适用于 DAG 和 二分图。
- Dilworth 定理
偏序集:可以理解为一个 DAG,满足对于任意三个点 \(x,y,z\),如果存在连边 \(x->y,y->z\),那么一定存在连边 \(x->z\)。
反链:指一个偏序集上的点集,满足任意两个点之间没有一条有向路径可达。
定理:对于一个偏序集,它的最大反链 \(=\) 最小路径覆盖。
匹配集
边集
最大匹配:选择最多的边,使得任意两条边没有公共点。
完美匹配:任意一个图上的点都在匹配中。
一般图的匹配 和 二分图匹配都有多项式算法。
支配集
点集
最小支配集:选择最少的点,使得图上任意一点都与选择的点有连边。
独立集
点集
最大独立集:选择最多的点,使得选择的任意两点之间都没有连边。
关于输出方案
二分图匹配
暴力遍历所有边,选出残余流量为 \(0\) 的边即可。
最小路径覆盖
在二分图上 dfs,每次只走未走过且剩余流量为 \(0\) 的边,每 dfs 一次就会形成一条路径。
二分图最小点覆盖,最大独立集,dilworth所求原图的最大独立集
从每一个非匹配左部点出发,沿着非匹配边正向进行遍历,沿着匹配边反向进行遍历,经过的点打上标记。选取左部点中没有被标记过的点,右部点中被标记过的点,则这些点可以形成该二分图的最小点覆盖。
以下内容来自 祭祀 的题解。
先求出二分图的任意一组最大匹配。我们 dfs 左边所有未匹配的点,每次 dfs 从左往右只走未匹配边,从右往左则只走匹配边(注意最后的搜索树可能不止是一条链,也可能是一棵树。也就是说要 dfs 当前可找到的所有未匹配边)。
二分图的一种最小点覆盖就是左侧的未访问点加上右侧的已访问点;二分图的一种最大独立集就是这些点的补集(对于二分图所有点全集);而原图的一种最大独立集就是由所有其编号对应的二分图左侧点和右侧点都在二分图最大独立集中的点组成(或者说由所有其编号对应的二分图左侧点已访问且右侧点未访问的点组成)。
匈牙利算法
每次匹配复杂度为 \(O(m)\),总时间复杂度为 \(O(nm)\)。
Dinic 算法
二分图上时间复杂度为 \(O(m\sqrt n)\)。
#include<bits/stdc++.h>
using namespace std;
const int N=1e4+5;
const int M=5e5+5;
int head[N],len;
struct E{
int to,next;
}e[M];
void add(int u,int v){
e[++len]=E{v,head[u]};head[u]=len;
}
int n,m,ee;
int vis[N],mat[N];
int dfs(int u){//mat下标为右部点,记录匹配到的左部点
for(int i=head[u];i;i=e[i].next){
int v=e[i].to;
if(vis[v]) continue;
vis[v]=1;
if(!mat[v]||dfs(mat[v])){mat[v]=u;return 1;}
}
return 0;
}
void solve(){
int ans=0;
for(int i=1;i<=n;i++){
for(int j=1;j<=n+m;j++) vis[j]=0;
ans+=dfs(i);
}
cout<<ans;
}
signed main(){
cin>>n>>m>>ee;
for(int i=1;i<=ee;i++){
int u,v;cin>>u>>v;add(u,v+n);
}
solve();
return 0;
}
最大流(Dinic)
#include<bits/stdc++.h>
using namespace std;
#define int long long
const int inf=1e12;
const int N=1e5+5;
const int M=2e6+5;
int head[N],len;
struct E{
int to,next,w;
}e[M];
void add(int u,int v,int w){//要写 0,前面不要写错了!
e[++len]=E{v,head[u],w};head[u]=len;
e[++len]=E{u,head[v],0};head[v]=len;
}
int dis[N],cur[N];
void bfs(){
for(int i=1;i<=t;i++) dis[i]=inf,cur[i]=head[i];
dis[s]=0;queue<int> q;q.push(s);
while(!q.empty()){
int u=q.front();q.pop();
for(int i=head[u];i;i=e[i].next){
int v=e[i].to;
if(dis[v]>dis[u]+1&&e[i].w){
dis[v]=dis[u]+1;q.push(v);
}
}
}
}
int dfs(int u,int rs){
if(u==t) return rs;
int ans=0;
for(int i=cur[u];i;i=cur[u]=e[i].next){
int v=e[i].to,w=e[i].w;
if(dis[v]==dis[u]+1&&w){
int d=dfs(v,min(rs,w));
if(!d){dis[v]=0;continue;}
ans+=d;rs-=d;
e[i].w-=d;e[i^1].w+=d;
if(!rs) return ans;
}
}return ans;
}
int flow(){
int ans=0;
while(1){
bfs();
if(dis[t]>=inf) return ans;
ans+=dfs(s,inf);
}
}
最大流(ISAP)
int head[N],len=1;
struct E{
int to,next,w,op;
}e[M];
void add(int u,int v,int w){
e[++len]=E{v,head[u],w,0};head[u]=len;
e[++len]=E{u,head[v],0,1};head[v]=len;
}
int cur[N],dis[N],gap[N];//gap[x]表示满足dis[u]=x的u的个数
void bfs(){//反向bfs求最短路
for(int i=1;i<=t;i++) dis[i]=inf,cur[i]=head[i];
dis[t]=0;queue<int> q;q.push(t);
while(!q.empty()){
int u=q.front();q.pop();gap[dis[u]]++;
for(int i=head[u];i;i=e[i].next){
if(!e[i].op) continue;//判断是否为反向边
int v=e[i].to;
if(dis[v]>dis[u]+1) dis[v]=dis[u]+1,q.push(v);
}
}
}
int dfs(int u,int rs){
if(u==t) return rs;
int ans=0;
for(int i=cur[u];i;i=cur[u]=e[i].next){
int v=e[i].to,w=e[i].w;
if(w&&dis[u]==dis[v]+1){
int d=dfs(v,min(rs,w));
ans+=d;rs-=d;e[i].w-=d;e[i^1].w+=d;
if(!rs) return ans;
}
}
if(!--gap[dis[u]]) dis[s]=t+1;
gap[++dis[u]]++;
cur[u]=head[u];//这里要修改cur
return ans;
}
int flow(){
bfs();
int ans=0;
while(dis[s]<=t) ans+=dfs(s,inf);
return ans;
}
费用流(原始对偶算法)
#include<bits/stdc++.h>
using namespace std;
#define int long long
#define pii pair<int,int>
const int inf=1e9;
const int N=1e5+5;
const int M=1e6;
int head[N],len=1;
struct E{
int u,to,next,w,cst;
}e[M];
void add(int u,int v,int w,int cst){
e[++len]=E{u,v,head[u],w,cst};head[u]=len;
e[++len]=E{v,u,head[v],0,-cst};head[v]=len;
}
priority_queue<pii,vector<pii>,greater<pii> > q;
int h[N],dis[N],pr[N],vis[N];
int s,t;
void spfa(int dis[]){
for(int i=1;i<=t;i++) dis[i]=inf,vis[i]=0;
dis[s]=0;
queue<int> q;
q.push(s);
vis[s]=1;
while(!q.empty()){
int u=q.front();q.pop();
vis[u]=0;
for(int i=head[u];i;i=e[i].next){
int v=e[i].to,w=e[i].w,cst=e[i].cst;
if(w&&dis[v]>dis[u]+cst){
dis[v]=dis[u]+cst;
if(!vis[v]) q.push(v),vis[v]=1;
}
}
}
}
void dij(){
for(int i=1;i<=t;i++) dis[i]=inf,vis[i]=0;
dis[s]=0;
q.push({0,s});
while(!q.empty()){
int u=q.top().second;q.pop();
if(vis[u]) continue;
vis[u]=1;
for(int i=head[u];i;i=e[i].next){
int v=e[i].to,w=e[i].w,cst=e[i].cst+h[u]-h[v];
if(w&&dis[v]>dis[u]+cst){//按照费用跑最短路,注意势能为 h[u]-h[v]
dis[v]=dis[u]+cst;pr[v]=i;q.push({dis[v],v});
}
}
}
for(int i=1;i<=t;i++) h[i]=h[i]+dis[i];
}
pair<int,int> dfs(){//mn流量,ds费用
int p=t;
int mn=inf,ds=0;
while(p!=s){
int i=pr[p];ds+=e[i].cst;
mn=min(mn,e[i].w);
p=e[i^1].to;
}
p=t;
while(p!=s){
int i=pr[p];
p=e[i^1].to;
e[i].w-=mn;e[i^1].w+=mn;
}
return pair<int,int>{mn,ds};
}
void flow(int s,int t){
spfa(h);
while(1){
dij();
if(dis[t]==inf) return;
pair<int,int> tmp=dfs();
}
}