Dinic算法 模板
const int maxn = 1e3 + 7;
const int INF = 0x3f3f3f3f;
struct Edge
{
int from, to, cap, flow;
};
struct Dinic
{
int n, m, s, t;
vector<Edge> edges;
vector<int> G[maxn];
bool vis[maxn];
int d[maxn];
int cur[maxn];
void AddEdge(int from, int to, int cap, int c = 0)
{
edges.push_back(Edge{from, to, cap, 0});
edges.push_back(Edge{to, from, c, 0});
m = edges.size();
G[from].push_back(m - 2);//从0开始建边
G[to].push_back(m - 1);
}
bool BFS()//用来建立分层图的
{
memset(vis, 0, sizeof(vis));
queue<int> q;
q.push(s);
d[s] = 0;//源点d为0
vis[s] = 1;
while (!q.empty())
{
int u = q.front();
q.pop();
for (int i = 0; i < G[u].size(); i++){
Edge &e = edges[G[u][i]];
if (!vis[e.to] && e.cap > e.flow)//e.cap>e.flow是还有流量的意思,没有被抵消
{
vis[e.to] = 1;
d[e.to] = d[u] + 1;//建立分层图
q.push(e.to);
}
}
}
return vis[t];//如果是0,说明流干了,再也到不了汇点了
}
int DFS(int u, int dist)
{
if (u==t||dist==0)//到了终点
return dist;
int flow = 0, f;
for (int &i = cur[u]; i < G[u].size(); i++){
Edge &e = edges[G[u][i]];
if (d[u] + 1 == d[e.to] && (f = DFS(e.to, min(dist, e.cap - e.flow))) > 0)
{//d是用来判断分层图是否合法,f是当前这条流和原来这条流选min
e.flow += f; //这条边的流过的流量+f
edges[G[u][i]^1].flow -= f; //反边的流量-f
flow += f; //已有流量
dist -= f;
if (!dist)//这条路径的流量是无穷的
break;
}
}
return flow;
}
int Maxflow(int s, int t)
{
this->s = s;
this->t = t;
int flow = 0;
while (BFS()){//建立个分层图
memset(cur, 0, sizeof(cur));
flow += DFS(s, INF);
}
return flow;
}
};
二分图匹配问题
建图:
左子集的点与源点相连,右子集的点与汇点相连
所有的边权都是1
匈牙利算法
#include<bits/stdc++.h>
using namespace std;
#define N 2010
int n,m,e,ans;
int vis[N][N];
int ask[N],matched[N];//match是匹配了谁
inline bool found(int x){ //dfs找增广路
for (int i=m+1;i<=n;i++)
if (vis[x][i]){
if (ask[i]) continue;
ask[i] = 1;
if (!matched[i] || found(matched[i])) {
matched[i] = x ;
return true;
}
}
return false;
}
inline void match(){
int cnt = 0;//cnt是计数器
memset(matched,0,sizeof(matched));
for (int i = 1;i<=m; i++){
memset(ask,0,sizeof(ask));
if (found(i)) cnt++; //找到了就加1
}
ans = cnt;
}
int main()
{
cin>>m>>n;
while(1){
int u,v;cin>>u>>v;
if(u==-1&&v==-1)break;
vis[u][v]=1;
}
match();
cout<<ans<<'\n';
for(int i=1;i<=n;i++){
if(matched[i])
cout<<matched[i]<<" "<<i<<endl;
}
return 0;
}
Dinic最大流
#include<bits/stdc++.h>
using namespace std;
#define int long long
const int maxn = 5e3 + 7;
const int INF = 0x3f3f3f3f;
int T;
struct Edge
{
int from, to, cap, flow;
};
struct Dinic
{
int n, m, s, t;
vector<Edge> edges;
vector<int> G[maxn];
bool vis[maxn];
int d[maxn];
int cur[maxn];
void AddEdge(int from, int to, int cap, int c = 0)
{
edges.push_back(Edge{from, to, cap, 0});
edges.push_back(Edge{to, from, c, 0});
m = edges.size();
G[from].push_back(m - 2);
G[to].push_back(m - 1);
}
bool BFS()
{
memset(vis, 0, sizeof(vis));
queue<int> q;
q.push(s);
d[s] = 0;
vis[s] = 1;
while (!q.empty())
{
int u = q.front();
q.pop();
for (int i = 0; i < G[u].size(); i++)
{
Edge &e = edges[G[u][i]];
if (!vis[e.to] && e.cap > e.flow)
{
vis[e.to] = 1;
d[e.to] = d[u] + 1;
q.push(e.to);
}
}
}
return vis[t];
}
int DFS(int u, int dist)
{
if (u == t || dist == 0)
return dist;
int flow = 0, f;
for (int &i = cur[u]; i < G[u].size(); i++)
{
Edge &e = edges[G[u][i]];
if (d[u] + 1 == d[e.to] && (f = DFS(e.to, min(dist, e.cap - e.flow))) > 0)
{
e.flow += f;
edges[G[u][i] ^ 1].flow -= f;
flow += f;
dist -= f;
if (!dist)
break;
}
}
return flow;
}
int Maxflow(int s, int t){
this->s = s;
this->t = t;
int flow = 0;
while (BFS()){
memset(cur, 0, sizeof(cur));
flow += DFS(s, INF);
}
cout<<flow<<endl;
for(auto mm: edges){
if(mm.from&&mm.to!=T)
if(mm.flow>0)
cout<<mm.from<<" "<<mm.to<<endl;
}
return flow;
}
};
signed main()
{
Dinic D;
int n,m;cin>>m>>n;
for(int i=1;i<=m;i++){
D.AddEdge(0,i,1);
}
for(int i=m+1;i<=n;i++){
D.AddEdge(i,n+1,1);
}
T=n+1;
while(1){
int u,v;cin>>u>>v;
if(u==-1&&v==-1)break;
D.AddEdge(u,v,1);
}
D.Maxflow(0,n+1);
return 0;
}
最大权闭合子图
建图:
正点权与源点相连,边权为点权;负点权与汇点相连,边权为点权的绝对值
正点权与负点权相连的部分为正无穷
输出:
输出用vis,只要vis为1,说明在最后一次建图的时候有参与
所以权值为正的点和权值为负的点按照同样的方法就能输出了
参考: 最大权闭合子图
结论:最大权闭合子图的权值等于所有正权点之和减去最小割
关于建图:
与源点相连的是点权为正的,与汇点相连的点权是负的,首先先把正的全加起来,然后再去算减去的最小割。如果有一条边正的点权比负的点权的绝对值小,那么sum减去最小割后,就相当于这条边没选过。
关于输出:
vis表示这一条边一定被走过,所以输出vis
#include<bits/stdc++.h>
using namespace std;
#define int long long
const int maxn = 5e3 + 7;
const int INF = 0x3f3f3f3f;
int T;
struct Edge{
int from, to, cap, flow;
};
struct Dinic
{
int n, m, s, t;
vector<Edge> edges;
vector<int> G[maxn];
bool vis[maxn];
int d[maxn];
int cur[maxn];
void AddEdge(int from, int to, int cap, int c = 0){
edges.push_back(Edge{from, to, cap, 0});
edges.push_back(Edge{to, from, c, 0});
m = edges.size();
G[from].push_back(m - 2);
G[to].push_back(m - 1);
}
bool BFS(){
memset(vis, 0, sizeof(vis));
queue<int> q;
q.push(s);
d[s]=0;
vis[s]=1;
while (!q.empty()){
int u = q.front();
q.pop();
for (int i = 0; i < G[u].size(); i++){
Edge &e = edges[G[u][i]];
if (!vis[e.to]&&e.cap> e.flow){
vis[e.to]=1;
d[e.to]=d[u]+1;
q.push(e.to);
}
}
}
return vis[t];
}
int DFS(int u, int dist){
if (u == t || dist == 0)
return dist;
int flow = 0, f;
for (int &i=cur[u];i<G[u].size(); i++){
Edge &e = edges[G[u][i]];
if (d[u]+1==d[e.to]&&(f=DFS(e.to,min(dist,e.cap - e.flow))) > 0){
e.flow += f;
edges[G[u][i]^1].flow -= f;
flow+=f;
dist-=f;
if (!dist)break;
}
}
return flow;
}
int Maxflow(int s, int t,int n,int m){
this->s = s;
this->t = t;
int flow = 0;
while (BFS()){
memset(cur,0,sizeof(cur));
flow += DFS(s,INF);
}
set<int> ss;
for(int i=1;i<=n;i++)
if(vis[i]) cout<<i<<' ';
cout<<endl;
for(int i=1;i<=m;i++)
if(vis[i+n]) cout<<i<<' ';
cout<<'\n';
return flow;
}
};
int a[110];
signed main()
{
Dinic D;
int n,m,sum = 0;
cin>>n>>m;
int s=0,t=n+m+1;
for(int i=1;i<=n;i++){
int x;cin >> x;
D.AddEdge(s,i,x);
sum+=x;
char tools[10000];
memset(tools,0,sizeof(tools));
cin.getline(tools,10000);
int ulen=0,tool;
while (sscanf(tools + ulen, "%d", &tool) == 1){
D.AddEdge(i,tool+n,INF);//中间的部分
if (tool == 0) ulen++;
else {
while (tool) {
tool /= 10;
ulen++;
}
}
ulen++;
}
}
for (int i=1;i<=m;i++){
int x;cin>>x;
D.AddEdge(i+n,t,x);
}
int ans=D.Maxflow(s,t,n,m);
printf("%d\n", sum-ans);
return 0;
}
有向无环图最小路径覆盖(无交点)
参考:最小路径覆盖问题(网络流24题) - Brave_Cattle - 博客园 (cnblogs.com)
建图:
同二分图匹配的建图方式,但右子集中的点序号是自己+n。
输出:
同二分图匹配的输出方式 ,但注意序号
定理:
最小路径覆盖数=|G|-二分图最大匹配数(|G|是有向图中的总点数)
首先我们知道,在二分图中一个点就代表者一条路径。那么如果此时二分图内没有连边,这个公式是成立的。每当二分图内增加一条边,最大匹配数就会+1,而一条匹配边会连接二分图中的两个点,那么两个点间本来有两条路径覆盖,就变成了一条。同理,每加入一条边匹配数就会+1,路径覆盖数就会-1。所以这个公式是成立的。但是这个公式是对二分图适用的,如何将它转化到这个问题上来呢?
这时我们先将一个点拆A成出点Ax和入点Ay,那么在连有向边A -> B的时候就将Ax连向By。就将有向图变成了一个二分图。
匈牙利算法
#include<bits/stdc++.h>
using namespace std;
#define N 601
int n,m,e,ans;//n是左边的人数,m是右边的人数,e是边数,ans是匹配数
int vis[N][N];
int ask[N],matched[N];//match是匹配了谁
inline bool found(int x){ //dfs找增广路
for (int i = n+1 ; i<=n+n ; i++){
if (vis[x][i]){
if (ask[i]) continue;
ask[i] = 1;
if (!matched[i] || found(matched[i])) {
matched[i] = x ;
return true;
}
}
}
return false;
}
inline void match(){
int cnt = 0;//cnt是计数器
memset(matched,0,sizeof(matched));
for (int i = 1 ; i <= n ; i++){
memset(ask,0,sizeof(ask));
if (found(i)) cnt++; //找到了就加1
}
ans = cnt;
}
bool print[301];
void P(int x){
if(x==0)return ;
P(matched[x+n]);
cout<<x<<' ';
print[x]=1;
}
int main()
{
cin>>n>>m;
for(int i=1;i<=m;i++){
int u,v;cin>>u>>v;
vis[u][v+n]=1;
}
match();
for(int i=2*n;i>n;i--){
if(!print[i-n]){
P(i-n);cout<<endl;
}
}
cout<<n-ans;
return 0;
}
Dinic算法
#include<bits/stdc++.h>
using namespace std;
#define N 601
int matched[N];
const int maxn = 1e3 + 7;
const int INF = 0x3f3f3f3f;
struct Edge
{
int from, to, cap, flow;
};
struct Dinic
{
int n, m, s, t;
vector<Edge> edges;
vector<int> G[maxn];
bool vis[maxn];
int d[maxn];
int cur[maxn];
void AddEdge(int from, int to, int cap, int c = 0)
{
edges.push_back(Edge{from, to, cap, 0});
edges.push_back(Edge{to, from, c, 0});
m = edges.size();
G[from].push_back(m - 2);//从0开始建边
G[to].push_back(m - 1);
}
bool BFS()
{
memset(vis, 0, sizeof(vis));
queue<int> q;
q.push(s);
d[s] = 0;
vis[s] = 1;
while (!q.empty())
{
int u = q.front();
q.pop();
for (int i = 0; i < G[u].size(); i++)
{
Edge &e = edges[G[u][i]];
if (!vis[e.to] && e.cap > e.flow){
vis[e.to] = 1;
d[e.to] = d[u] + 1;
q.push(e.to);
}
}
}
return vis[t];
}
int DFS(int u, int dist)
{
if (u == t || dist == 0)
return dist;
int flow = 0, f;
for (int &i = cur[u]; i < G[u].size(); i++)
{
Edge &e = edges[G[u][i]];
if (d[u] + 1 == d[e.to] && (f = DFS(e.to, min(dist, e.cap - e.flow))) > 0)
{
e.flow += f; //这条边的流过的流量+f
edges[G[u][i]^1].flow -= f; //反边的流量-f
flow += f; //已有流量
dist -= f;
if (!dist)//这条路径的流量是无穷的
break;
}
}
return flow;
}
int Maxflow(int s, int t,int n)
{
this->s = s;
this->t = t;
int flow = 0;
while (BFS()){
memset(cur, 0, sizeof(cur));
flow += DFS(s, INF);
}
for(auto mm: edges){
if(mm.from&&mm.to!=t)
if(mm.flow>0){
matched[mm.to]=mm.from;
//cout<<mm.from<<" "<<mm.to<<endl;
}
}
return flow;
}
};
bool print[301];
void P(int x,int n){
if(x==0)return ;
P(matched[x+n],n);
cout<<x<<' ';
print[x]=1;
}
int main()
{
Dinic D;
int n,m;cin>>n>>m;
for(int i=1;i<=n;i++){
D.AddEdge(0,i,1);
}
for(int i=1+n;i<=2*n;i++){
D.AddEdge(i,2*n+1,1);
}
for(int i=1;i<=m;i++){
int u,v;cin>>u>>v;
D.AddEdge(u,v+n,1);
}
int ans=D.Maxflow(0,2*n+1,n);
for(int i=2*n;i>n;i--){
if(!print[i-n]){
P(i-n,n);
cout<<endl;
}
}
cout<<n-ans;
return 0;
}
二分图多重匹配
建图:
因为一道题只可以有一个,所以源点和试题的边的容量为1
同理一道题只能满足一种类型,所以试题和类型的边的容量也为1
而需要满足的类型是有多个的,所以类型与汇点的边的容量为所需类型的数量