网络流复习笔记
主要用来复习一下自己之前学过的网络流。
因为当时都是直接抄的题解,莫得印象。所以写篇博客加强记忆。
最大流
LuoguP3254 圆桌问题
先分析题目。
比较明显,如果我们用网络流的思路去分析这个问题。将会将每个单位和每个餐桌都看做点。然后由于每个单位的人理论上可以坐在任意餐桌,同时对于一个餐桌一个单位只能有一个人。所以每个单位向每个餐桌连一条容量为\(1\)的边。
第\(i\)个单位最多有\(r_i\)人,所以从原点向它连容量为\(r_i\)的边。
餐桌同样考虑。
如果最大流等于总人数.说明有可行方案。
#include<cstdio> #include<cstring> #include<cctype> #include<iostream> #include<algorithm> #include<queue> #include<vector> using namespace std; const int N = 1e5 + 3; const int M = 2e5 + 3; const int INF = 2e9; struct edge{ int to; int from; int nxt; int flow; }e[N << 1]; vector <int> G[N]; int head[N],high[N]; int ci[N],ri[N],cur[N]; int n,m,s,t; int tot = 1,sum; inline int read(){ int v = 0,c = 1;char ch = getchar(); while(!isdigit(ch)){ if(ch == '-') c = -1; ch = getchar(); } while(isdigit(ch)){ v = v * 10 + ch - 48; ch = getchar(); } return v * c; } inline void add(int x,int y,int flow){ e[++tot].to = y; e[tot].from = x; e[tot].flow = flow; e[tot].nxt = head[x]; head[x] = tot; } inline bool bfs(){ queue <int> q; for(int i = 1;i <= n + m + 2;++i) high[i] = 0; q.push(s);high[s] = 1; while(!q.empty()){ int k = q.front();q.pop(); for(int i = head[k];i;i = e[i].nxt){ int y = e[i].to; if(!high[y] && e[i].flow > 0) high[y] = high[k] + 1,q.push(y); } } return high[t] != 0; } inline int dfs(int x,int dis){ if(x == t ) return dis; for(int &i = cur[x];i;i = e[i].nxt){ int y = e[i].to; if(high[y] == high[x] + 1 && e[i].flow > 0){ int flow = dfs(y,min(dis,e[i].flow)); if(flow > 0){ e[i].flow -= flow; e[i ^ 1].flow += flow; return flow; } } } return 0; } inline int dinic(){ int res = 0; while(bfs()){ for(int i = 1;i <= n + m + 2;++i) cur[i] = head[i]; while(int now = dfs(s,INF)) res += now; } return res; } int main(){ n = read(),m = read(); s = n + m + 1,t = s + 1; for(int i = 1;i <= n;++i){ ri[i] = read(); sum += ri[i]; add(s,i,ri[i]); add(i,s,0); } for(int i = 1;i <= m;++i){ ci[i] = read(); add(i + n,t,ci[i]); add(t,i + n,0); } for(int i = 1;i <= n;++i) for(int j = 1;j <= m;++j) add(i,j + n,1),add(j + n,i,0); if(dinic() != sum) printf("0\n"); else{ printf("1\n"); for(int i = 2;i <= tot;i += 2){ if(e[i].to != s && e[i ^ 1].to != s && e[i].to != t && e[i ^ 1].to != t) if(e[i].flow == 0) G[e[i].from].push_back(e[i].to - n); } for(int i = 1;i <= n;++i){ for(int j = 0;j < (int)G[i].size();++j) printf("%d ",G[i][j]); printf("\n"); } } return 0; }
LuoguP2764 最小路径覆盖问题
依旧是最大流的典型例题。
我们设总的覆盖路径条数为\(S\),覆盖了\(C\)条边,共有\(N\)个点
那么有:
\(S = N - C\)
所以我们要最小化\(S\),只需要最大化\(C\)即可。
我们发现,由于每个点必须且只能被覆盖一次。所以这个点的所有入边和出边只能贡献一个。我们将点\(V\)拆成两个点\(v\)与\(v'\)分别表示\(V\)点的入度与出度
如果原图中存在一条边\((u,v)\)
那么就连一条从\(u'\)到\(v\)的流量为\(1\)的边。
借用一下_\(rqy\)的图
大概就是这个样子。
本质上貌似是一个二分图的最大匹配问题
#include<iostream> #include<cstdio> #include<cstring> #include<cctype> #include<algorithm> #include<vector> #include<queue> using namespace std; const int N = 1e3 + 3; const int M = 1e5 + 3; const int INF = 2e9; struct edge{ int to; int from; int nxt; int flow; }e[M << 1]; int head[N],high[N]; int pre[N],nt[N],cur[N],sta[N]; int n,m,tot = 1,s,t,top; vector <int> G[N]; inline int read(){ char ch = getchar();int v = 0,c = 1; while(!isdigit(ch)){ if(ch == '-') c = -1; ch = getchar(); } while(isdigit(ch)){ v = v * 10 + ch - 48; ch = getchar(); } return v * c ; } inline void add(int x,int y,int flow){ e[++tot].to = y; e[tot].flow = flow; e[tot].from = x; e[tot].nxt = head[x]; head[x] = tot; } inline bool bfs(){ queue <int> q; for(int i = 1;i <= n + n + 2;++i) high[i] = 0; q.push(s);high[s] = 1; while(!q.empty()){ int k = q.front();q.pop(); for(int i = head[k];i;i = e[i].nxt){ int y = e[i].to; if(!high[y] && e[i].flow > 0) high[y] = high[k] + 1,q.push(y); } } return high[t] != 0; } inline int dfs(int x,int dis){ if(x == t) return dis; for(int &i = cur[x];i;i = e[i].nxt){ int y = e[i].to; if(high[y] == high[x] + 1 && e[i].flow > 0){ int flow = dfs(y,min(dis,e[i].flow)); if(flow > 0){ e[i].flow -= flow; e[i ^ 1].flow += flow; return flow; } } } return 0; } inline int dinic(){ int res = 0; while(bfs()){ for(int i = 1;i <= n + n + 2;++i) cur[i] = head[i]; while(int now = dfs(s,INF)) res += now; } return res; } inline void work(int x){ G[x].push_back(sta[x]); int now = sta[x]; bool flag = 1; while(flag){ bool find = 0; for(int i = head[now];i;i = e[i].nxt){ if(e[i].flow == 0 && e[i].to != s){ G[x].push_back(e[i].to - n); now = e[i].to - n; find = 1; break; } } flag = find; } } int main(){ n = read(),m = read(); s = n + n + 1,t = n + n + 2; for(int i = 1;i <= m;++i){ int x = read(),y = read(); add(x,y + n,1); add(y + n,x,0); } for(int i = 1;i <= n;++i) add(s,i,1),add(i,s,0); for(int i = n + 1;i <= n << 1;++i) add(i,t,1),add(t,i,0); int sum = n - dinic(); for(int i = head[t];i;i = e[i].nxt){ int now = i ^ 1; if(e[now].flow == 1) sta[++top] = e[now].from - n; } for(int i = 1;i <= top;++i) work(i); for(int i = 1;i <= top;++i){ for(int j = 0;j < (int)G[i].size();++j) printf("%d ",G[i][j]); printf("\n"); } printf("%d\n",sum); return 0; }
最小割
首先,模型解释
把图上的点划分成两个集合,使得 \(S\) 和\(T\) 不在同一个集合中,并最小化从 \(S\) 所在集合到 \(T\) 所在集合的所有边的边权之和
或者说删掉代价最小的边使得图不连通
最大权闭合子图
共有\(n\)个物品,每个物品都有其价值(可以为负),有部分限制比如说(选\(i\)必须选\(j\),即\(i\)依赖于\(j\)),求选择物品的最大价值。
我们考虑将最小割中的两个集合\(S\),\(T\)分别看作选与不选。
将所有的正权物品向汇点连一条流量为其权值的边,表示不选择物品会损失其权值。
将源点向所有的负权物品连一条流量为其权值相反数的边,表示选择这个物品会获得其权值也就是损失其相反数的收益。
如果物品\(i\)依赖于\(j\),那么就从\(j - i\)连一条权值为\(+\infty\)表示\(i,j\)在同一集合
最后将所有正权之和减去最小割即可
luogu2762太空飞行计划问题
根据上面的最大权闭合子图的模型,我们便很容易建出图来
将实验看做正权物品,设备看做负权物品
再次借用_\(rqy\)的图
我的理解:
满流的边是被割掉的边。左边\(l_3,l_4\)满流表示购买这两个设备
右边\(E_2,E_1\)满流表示不做这两个实验
#include<cstdio> #include<cstring> #include<iostream> #include<cctype> #include<queue> #include<vector> #include<set> using namespace std; const int N = 1e2 + 2; const int INF = 2e9; struct edge{ int to; int nxt; int flow; }e[N * N]; int n,m; int head[N],cur[N],high[N]; int s,t,tot = 1; bool flag; int sum = 0; vector <int> A1[N * N],ans1; set <int> ans2; inline void build(int x,int y,int z){ e[++tot].to = y; e[tot].flow = z; e[tot].nxt = head[x]; head[x] = tot; } inline void add(int x,int y,int z){ build(x,y,z); build(y,x,0); } inline bool bfs(){ queue <int> q; for(int i = 1;i <= t;++i) high[i] = 0; high[s] = 1;q.push(s); while(!q.empty()){ int k = q.front();q.pop(); for(int i = head[k];i;i = e[i].nxt){ int y = e[i].to; if(!high[y] && e[i].flow > 0) high[y] = high[k] + 1,q.push(y); } } return high[t]; } inline int dfs(int x,int dis){ if(x == t) return dis; for(int &i = cur[x];i;i = e[i].nxt){ int y = e[i].to; if(high[y] == high[x] + 1 && e[i].flow > 0){ int flow = dfs(y,min(dis,e[i].flow)); if(flow > 0){ e[i].flow -= flow; e[i ^ 1].flow += flow; return flow; } } } return 0; } inline int dinic(){ int res = 0; while(bfs()){ for(int i = 1;i <= t;++i) cur[i] = head[i]; while(int now = dfs(s,INF)) res += now; }// for(int i = 1;i <= n + m;++i) cout << high[i] << endl; return res; } int main(){ scanf("%d%d",&m,&n); s = n + m + 1,t =n + m + 2; for(int i = 1;i <= m;++i){ int x; scanf("%d",&x);sum += x; add(n + i,t,x); char tools[10000]; memset(tools,0,sizeof tools); cin.getline(tools,10000); int ulen=0,tool; while (sscanf(tools+ulen,"%d",&tool)==1)//之前已经用scanf读完了赞助商同意支付该实验的费用 { A1[i].push_back(tool); add(tool,i + n,INF); if (tool==0) ulen++; else { while (tool) { tool/=10; ulen++; } } ulen++; } } for(int i = 1;i <= n;++i){ int x;scanf("%d",&x); add(s,i,x); } int now = dinic(); // for(int i = 1;i <= n + m;++i) cout << high[i] << endl; for(int i = 1;i <= m;++i) if(high[i + n] == 0) printf("%d ",i); printf("\n"); for(int i = 1;i <= n;++i) if(high[i] == 0)printf("%d ",i); printf("\n%d\n",sum - now); return 0; }
来源:https://www.cnblogs.com/wyxdrqc/p/10634375.html