最大流算法
最大流最小割的关系
POJ 3436 题目链接
题意
流水线上有N台机器装电脑,电脑有P个部件,每台机器有三个参数,产量,输入规格,输出规格;输入规格中0表示改部件不能有,1表示必须有,2无所谓;输出规格中0表示改部件没有,1表示有。问如何安排流水线(如何建边)使产量最高。
思路
拆点,将一个机器拆成入点和出点,入点到出点的容量就是边权
设置一个超级源点,超级源点到全0的入点容量为INF,设置超级终点,全1的出点到超级终点的容量为INF,
中间点:出点要和入点的1吻合,不能多不能少,容量设置为INF(出点的1比入点的1多就会出现环)
然后跑一遍最大流
最大流输出路径
#include <queue>
#include <cstdio>
#include <iostream>
#include <cstring>
using namespace std;
typedef long long ll;
const int INF = 0x3f3f3f3f;
const double eps = 1e-8;
const int N = 111;
const int M = 1e5 + 5;
struct edge{
int to,nex,len,old;
}e[M];
struct point{
int u,v,len;
}res[N];
struct node{
int value,in[N],out[N];
}s[N];
int head[N],cnt;
int se,ed,p,n;
int depth[N],vis[N];
void add(int u,int v,int len)
{
e[cnt].len = len;
e[cnt].to = v;
e[cnt].nex = head[u];
e[cnt].old = len;
head[u] = cnt++;
}
/// 层次网络
int bfs()
{
memset(depth,0,sizeof(depth));
queue<int> q;
q.push(se);
depth[se] = 1;
while(!q.empty()){
int u = q.front();
q.pop();
for (int i = head[u]; i != -1; i = e[i].nex){
int v = e[i].to;
if (depth[v] || e[i].len <= 0) continue;
depth[v] = depth[u] + 1;
q.push(v);
}
}
return depth[ed];
}
/// dfs查找所有增广路并做流量调整
int dfs(int u,int maxflow)
{
if (u == ed) return maxflow;
int ans = 0;
for (int i = head[u]; i != -1 && ans < maxflow; i = e[i].nex){
int v = e[i].to;
if (depth[v] != depth[u] + 1 || e[i].len == 0) continue;
int temflow = dfs(v,min(e[i].len,maxflow - ans));
e[i].len -= temflow; /// 前向弧流量减少
e[i ^ 1].len += temflow; /// 反向弧流量增加
ans += temflow;
if (ans == maxflow) break;
}
if (!ans) depth[u] = INF;
return ans;
}
int dinic()
{
int ans = 0;
while(bfs()){
ans += dfs(se,INF);
}
return ans;
}
bool judge(node a,node b)
{
for (int i = 1; i <= p; i++){
if ((b.in[i] == 1 && !a.out[i]) || (!b.in[i] && a.out[i])) return false;
}
return true;
}
bool link_start(node a)
{
for (int i = 1; i <= p; i++){
if (a.in[i] == 1) return false;
}
return true;
}
bool link_end(node a)
{
for (int i = 1; i <= p; i++){
if (!a.out[i]) return false;
}
return true;
}
int main()
{
while(scanf("%d %d",&p,&n) == 2){
memset(head,-1,sizeof(head));
cnt = 0;
se = 0,ed = 2 * n + 1;
for (int i = 1; i <= n; i++){
scanf("%d",&s[i].value);
for (int j = 1; j <= p; j++){
scanf("%d",&s[i].in[j]);
}
for (int j = 1; j <= p; j++){
scanf("%d",&s[i].out[j]);
}
add(i,i + n,s[i].value);
add(i + n,i,0);
if (link_start(s[i])){
add(se,i,INF);
add(i,se,0);
}
if (link_end(s[i])){
add(i + n,ed,INF);
add(ed,i + n,0);
}
}
for (int i = 1; i <= n; i++){
for (int j = 1; j <= n; j++){
if (i == j) continue;
if (judge(s[i],s[j])){
add(i + n,j,INF);
add(j,i + n,0);
}
}
}
int mx = dinic();
int ans = 0;
for (int u = n + 1; u <= 2 * n; u++){
for (int i = head[u]; i != -1; i = e[i].nex){
int v = e[i].to;
if (v == 0 || v > n) continue;
if (v + n == u) continue;
if (e[i].old > e[i].len){
res[ans].u = u - n;
res[ans].v = v;
res[ans].len = e[i].old - e[i].len;
ans++;
}
}
}
printf("%d %d\n",mx,ans);
for (int i = 0; i < ans; i++){
printf("%d %d %d\n",res[i].u,res[i].v,res[i].len);
}
}
return 0;
}
POJ 2516 题目链接
题意
有N个供应商,M个店主,K种物品。每个供应商对每种物品的的供应量已知,每个店主对每种物品的需求量的已知,从不同的供应商运送不同的货物到不同的店主手上需要不同的花费,又已知从供应商Mj送第kind种货物的单位数量到店主Ni手上所需的单位花费。
问:供应是否满足需求?如果满足,最小运费是多少?
思路
如果要将每个点的物品和供应商的物品都弄成点,那点太多了,肯定会超时的,因为物品是独立的,所以我们把每一种物品都跑一遍最小费用最大流然后求和即可
最小费用最大流SPFA模板
#include <iostream>
#include <cstring>
#include <cstdio>
#include <queue>
#include <algorithm>
using namespace std;
const int N = 111;
const int M = 1e5 + 7;
const int INF = 0x3f3f3f3f;
struct node{
int head,to,cost,cap,nex;
}e[M];
int need[N][N],goods[N][N];
int sum_need[N],sum_goods[N];
int cost[N][N][N];
int nv,se,ed;
bool vis[N];
int lowcost[N],pre[N];
int head[N],cnt;
void add(int u,int v,int cap,int cost)
{
e[cnt].head = u;
e[cnt].to = v;
e[cnt].cap = cap;
e[cnt].cost = cost;
e[cnt].nex = head[u];
head[u] = cnt++;
}
bool SPFA()
{
for (int i = 0; i < nv; i++){
vis[i] = 0;
lowcost[i] = INF;
}
queue<int> q;
q.push(se);
vis[se] = true;
lowcost[se] = 0;
while(!q.empty()){
int u = q.front();
q.pop();
vis[u] = false;
for (int i = head[u]; i != -1; i = e[i].nex){
int v = e[i].to;
if (e[i].cap && lowcost[v] > lowcost[u] + e[i].cost){
lowcost[v] = lowcost[u] + e[i].cost;
pre[v] = i;
if (!vis[v]){
vis[v] = true;
q.push(v);
}
}
}
}
return lowcost[ed] != INF;
}
int MCMF()
{
int ans = 0;
while(SPFA()){
int flow = INF,cost = 0;
for (int u = ed; u != se; u = e[pre[u]].head){
flow = min(flow,e[pre[u]].cap);
}
for (int u = ed; u != se; u = e[pre[u]].head){
e[pre[u]].cap -= flow;
e[pre[u] ^ 1].cap += flow;
cost += flow * e[pre[u]].cost;
}
ans += cost;
}
return ans;
}
bool judge(int k)
{
for (int i = 1; i <= k; i++){
if (sum_need[i] > sum_goods[i]) return true;
}
return false;
}
int main()
{
int n,m,p;
while(scanf("%d %d %d",&n,&m,&p) == 3 && (n || m || p)){
memset(sum_goods,0,sizeof(sum_goods));
memset(sum_need,0,sizeof(sum_need));
for (int i = 1; i <= n; i++){
for (int j = 1; j <= p; j++){
scanf("%d",&need[i][j]);
sum_need[j] += need[i][j];
}
}
for (int i = 1; i <= m; i++){
for (int j = 1; j <= p; j++){
scanf("%d",&goods[i][j]);
sum_goods[j] += goods[i][j];
}
}
for (int k = 1; k <= p; k++){
for (int i = 1; i <= n; i++){
for (int j = 1; j <= m; j++){
scanf("%d",&cost[k][i][j]);
}
}
}
if (judge(p)){
printf("-1\n");
continue;
}
se = 0,ed = n + m + 1,nv = n + m + 2;
int ans = 0;
for (int k = 1; k <= p; k++){
memset(head,-1,sizeof(head));
cnt = 0;
for (int i = 1; i <= m; i++){
add(se,i,goods[i][k],0);
add(i,se,0,0);
}
for (int i = 1; i <= n; i++){
add(i + m,ed,need[i][k],0);
add(ed,i,0,0);
}
for (int i = 1; i <= n; i++){
for (int j = 1; j <= m; j++){
add(j,i + m,INF,cost[k][i][j]);
add(i + m,j,0,-cost[k][i][j]);
}
}
ans += MCMF();
}
printf("%d\n",ans);
}
return 0;
}
UVA 10480 VJ题目链接
题意
这道题的意思要把一个图分成两部分,要把点1和点2分开。隔断每条边都有一个花费,求最小花费的情况下,应该切断那些边
思路
最小割
最小割输出路径
在残量网络中,将源点S能到达的点看作S集,其他点看作T集。如果边的一个点属于S集,另一个点属于T集,那么该边属于最小割边集。
也就是在depth数组中,属于S集的能够到达,属于T集的不能到达
#include <iostream>
#include <cstring>
#include <cstdio>
#include <queue>
#include <algorithm>
using namespace std;
const int N = 4444;
const int M = 1e5 + 7;
const int INF = 0x3f3f3f3f;
struct node{
int to,len,nex;
}e[M * 2];
int depth[N];
int se,ed;
int head[N],cnt;
void add(int u,int v,int len)
{
e[cnt].to = v;
e[cnt].len = len;
e[cnt].nex = head[u];
head[u] = cnt++;
}
/// 层次网络
int bfs()
{
memset(depth,0,sizeof(depth));
queue<int> q;
q.push(se);
depth[se] = 1;
while(!q.empty()){
int u = q.front();
q.pop();
for (int i = head[u]; i != -1; i = e[i].nex){
int v = e[i].to;
if (depth[v] || e[i].len <= 0) continue;
depth[v] = depth[u] + 1;
q.push(v);
}
}
return depth[ed];
}
/// dfs查找所有增广路并做流量调整
int dfs(int u,int maxflow)
{
if (u == ed) return maxflow;
int ans = 0;
for (int i = head[u]; i != -1 && ans < maxflow; i = e[i].nex){
int v = e[i].to;
if (depth[v] != depth[u] + 1 || e[i].len == 0) continue;
int temflow = dfs(v,min(e[i].len,maxflow - ans));
e[i].len -= temflow; /// 前向弧流量减少
e[i ^ 1].len += temflow; /// 反向弧流量增加
ans += temflow;
if (ans == maxflow) break;
}
if (!ans) depth[u] = INF;
return ans;
}
int dinic()
{
int ans = 0;
while(bfs()){
ans += dfs(se,INF);
}
return ans;
}
int main()
{
int n,m;
while(scanf("%d %d",&n,&m) == 2 && (n || m)){
memset(head,-1,sizeof(head));
cnt = 0;
se = 1,ed = 2;
for (int i = 0; i < m; i++){
int u,v,len;
scanf("%d %d %d",&u,&v,&len);
add(u,v,len);
add(v,u,len);
}
dinic();
for (int u = 1; u <= n; u++){
for (int i = head[u]; i != -1; i = e[i].nex){
int v = e[i].to;
if (depth[u] && !depth[v]){
printf("%d %d\n",u,v);
}
}
}
printf("\n");
}
return 0;
}
HDU 3605 题目链接
题意
给出每个人适合住的星球信息和该星球能住多少人 ,第一行给出n m 代表有 n 个人 m 个星球,然后接下来n行每行m个数字 1代表适合第 i 个星球 0 代表不适合第 i 个星球,最后一行m个数表示第 i 个星球最多可以住多少个人,问是不是所有人都可以住到星球上。
思路
因为人有1e5个,星球只有十个,要是每个人都弄成一个点,肯定就超时了,每个人最多只有(1<<10)种选择,就可以把他们看成一个点,流量就是人数,然后跑最大流即可
#include <cstring>
#include <iostream>
#include <queue>
#include <string>
#include <algorithm>
#include <cstdio>
#include <vector>
using namespace std;
const int N = 1e6 + 5;
const int M = 1e6 + 5;
const int INF = 0x3f3f3f3f;
struct edge{
int to,nex,len;
}e[M];
int head[N],cnt;
int se,ed;
int num[N];
int n,m;
int depth[N];
int input()
{
char ch;
int a = 0;
while((ch = getchar()) == ' ' || ch == '\n');
a += ch - '0';
while((ch = getchar()) != ' ' && ch != '\n')
{
a *= 10;
a += ch - '0';
}
return a;
}
void add(int u,int v,int len)
{
e[cnt].len = len;
e[cnt].to = v;
e[cnt].nex = head[u];
head[u] = cnt++;
}
/// 层次网络
int bfs()
{
memset(depth,0,sizeof(depth));
queue<int> q;
q.push(se);
depth[se] = 1;
while(!q.empty()){
int u = q.front();
q.pop();
for (int i = head[u]; i != -1; i = e[i].nex){
int v = e[i].to;
if (depth[v] || e[i].len <= 0) continue;
depth[v] = depth[u] + 1;
q.push(v);
}
}
return depth[ed];
}
/// dfs查找所有增广路并做流量调整
int dfs(int u,int maxflow)
{
if (u == ed) return maxflow;
int ans = 0;
for (int i = head[u]; i != -1 && ans < maxflow; i = e[i].nex){
int v = e[i].to;
if (depth[v] != depth[u] + 1 || e[i].len == 0) continue;
int temflow = dfs(v,min(e[i].len,maxflow - ans));
e[i].len -= temflow; /// 前向弧流量减少
e[i ^ 1].len += temflow; /// 反向弧流量增加
ans += temflow;
if (ans == maxflow) break;
}
if (!ans) depth[u] = INF;
return ans;
}
int dinic()
{
int ans = 0;
while(bfs()){
ans += dfs(se,INF);
}
return ans;
}
int main()
{
while(scanf("%d %d",&n,&m) == 2){
memset(num,0,sizeof(num));
for (int i = 1; i <= n; i++){
int ans = 0,x;
for (int j = 1; j <= m; j++){
x = input();
ans = ans * 2 + x;
}
num[ans]++;
}
memset(head,-1,sizeof(head));
cnt = 0;
int k = 1 << m;
se = 0,ed = k + 2 * m + 1;
for (int i = 0; i < k; i++){
if (num[i] == 0) continue;
add(se,i + 1,num[i]);
add(i + 1,se,0);
for (int j = 1; j <= m; j++){
if (i & (1 << (j - 1))){
add(i + 1,k + j,num[i]);
add(k + j,i + 1,0);
}
}
}
for (int i = 1; i <= m; i++){
int x;
scanf("%d",&x);
add(k + i,k + m + i,x);
add(k + m + i,k + i,0);
add(k + m + i,ed,INF);
add(ed,k + m + i,0);
}
if (dinic() == n) printf("YES\n");
else printf("NO\n");
}
return 0;
}
HDU 3081 题目链接
题意
n个男生,n个女生,接下来有 m个关系,u v表示第 u 个女生和第 v个男生可以配对,然后接下来有 f 个关系,u v表示第 u个女生和第v个女生是好友,如果 u 和 v可以配对,u 和 w是好友,那么w 和 v也是可以配对的.问知道这些人的关系,最多可以完全配对多少次(完全配对是指n个男生和n个女生都可以配对)?
思路
二分+并查集+最大流
#include <cstring>
#include <iostream>
#include <queue>
#include <string>
#include <algorithm>
#include <cstdio>
#include <vector>
using namespace std;
const int N = 2222;
const int M = 1e6 + 5;
const int INF = 0x3f3f3f3f;
struct edge{
int to,nex,len;
}e[M];
int head[N],cnt;
int se,ed;
int pre[N];
int n,m;
int depth[N];
int g[N][N];
void add(int u,int v,int len)
{
e[cnt].len = len;
e[cnt].to = v;
e[cnt].nex = head[u];
head[u] = cnt++;
}
/// 层次网络
int bfs()
{
memset(depth,0,sizeof(depth));
queue<int> q;
q.push(se);
depth[se] = 1;
while(!q.empty()){
int u = q.front();
q.pop();
for (int i = head[u]; i != -1; i = e[i].nex){
int v = e[i].to;
if (depth[v] || e[i].len <= 0) continue;
depth[v] = depth[u] + 1;
q.push(v);
}
}
return depth[ed];
}
/// dfs查找所有增广路并做流量调整
int dfs(int u,int maxflow)
{
if (u == ed) return maxflow;
int ans = 0;
for (int i = head[u]; i != -1 && ans < maxflow; i = e[i].nex){
int v = e[i].to;
if (depth[v] != depth[u] + 1 || e[i].len == 0) continue;
int temflow = dfs(v,min(e[i].len,maxflow - ans));
e[i].len -= temflow; /// 前向弧流量减少
e[i ^ 1].len += temflow; /// 反向弧流量增加
ans += temflow;
if (ans == maxflow) break;
}
if (!ans) depth[u] = INF;
return ans;
}
int dinic()
{
int ans = 0;
while(bfs()){
ans += dfs(se,INF);
}
return ans;
}
void floyd(int n)
{
for (int k = 1; k <= 2 * n; k++){
for (int i = 1; i <= 2 * n; i++){
for (int j = 1; j <= 2 * n; j++){
if (g[i][k] && g[k][j]) g[i][j] = 1;
}
}
}
}
int Find(int x)
{
return pre[x] == x ? x : pre[x] = Find(pre[x]);
}
void Union(int a,int b)
{
int x = Find(a),y = Find(b);
if (x != y){
pre[x] = y;
}
}
void build(int k,int n)
{
memset(head,-1,sizeof(head));
cnt = 0;
for (int i = 1; i <= n; i++){
add(se,i,k);
add(i,se,0);
for (int j = n + 1; j <= n + n; j++){
if (g[i][j]){
add(i,j,1);
add(j,i,0);
}
}
}
for (int i = n + 1; i <= n + n; i++){
add(i,ed,k);
add(ed,i,0);
}
for (int i = 1; i <= n; i++){
for (int j = 1; j <= n; j++){
if (i == j) continue;
if (Find(i) == Find(j)){
for (int k = n + 1; k <= n + n; k++){
if (!g[j][k] && g[i][k]){
add(j,k,1);
add(k,j,0);
g[j][k] = 1;
}
}
}
}
}
}
int main()
{
int t;
scanf("%d",&t);
while(t--){
int n,m,f;
scanf("%d %d %d",&n,&m,&f);
se = 0,ed = 2 * n + 1;
memset(g,0,sizeof(g));
for (int i = 0; i < m; i++){
int u,v;
scanf("%d %d",&u,&v);
g[u][v + n] = 1;
g[v + n][u] = 1;
}
for (int i = 1; i <= n; i++){
pre[i] = i;
}
for (int i = 0; i < f; i++){
int u,v;
scanf("%d %d",&u,&v);
Union(u,v);
}
int l = 0,r = n,ans;
while(l <= r){
int mid = (l + r) >> 1;
build(mid,n);
if (dinic() == n * mid){
ans = mid;
l = mid + 1;
}
else r = mid - 1;
}
printf("%d\n",ans);
}
return 0;
}
HDU 3416 题目链接
题意
在每次都走最短路的情况下,从A市到B市能走多少次(每条路只能用一次)
思路
先跑一遍最短路,把最短路径上的边都找出来(lowcost[v] = lowcost[u] + e[i].len),将这些边的流量都设为1,然后跑一遍最大流即可
#include <bits/stdc++.h>
using namespace std;
const int INF = 0x3f3f3f3f;
const double eps = 1e-8;
const int N = 1111;
const int M = 1e5 + 5;
struct edge{
int u,v,len;
}s[M];
struct node{
int to,nex,len;
}e[M * 4];
struct point{
int id,cost;
bool operator < (const point &p) const{
return cost > p.cost;
}
};
int head[N],cnt;
int se,ed;
int lowcost[N],depth[N];
void add(int u,int v,int len)
{
e[cnt].len = len;
e[cnt].to = v;
e[cnt].nex = head[u];
head[u] = cnt++;
}
void dijkstra()
{
memset(lowcost,0x3f,sizeof(lowcost));
priority_queue<point> q;
q.push({se,0});
lowcost[se] = 0;
while(!q.empty()){
point now = q.top();
q.pop();
int u = now.id;
for (int i = head[u]; i != -1; i = e[i].nex){
int v = e[i].to;
if (lowcost[v] > lowcost[u] + e[i].len){
lowcost[v] = lowcost[u] + e[i].len;
q.push({v,lowcost[v]});
}
}
}
}
/// 层次网络
int bfs()
{
memset(depth,0,sizeof(depth));
queue<int> q;
q.push(se);
depth[se] = 1;
while(!q.empty()){
int u = q.front();
q.pop();
for (int i = head[u]; i != -1; i = e[i].nex){
int v = e[i].to;
if (depth[v] || e[i].len <= 0) continue;
depth[v] = depth[u] + 1;
q.push(v);
}
}
return depth[ed];
}
/// dfs查找所有增广路并做流量调整
int dfs(int u,int maxflow)
{
if (u == ed) return maxflow;
int ans = 0;
for (int i = head[u]; i != -1 && ans < maxflow; i = e[i].nex){
int v = e[i].to;
if (depth[v] != depth[u] + 1 || e[i].len == 0) continue;
int temflow = dfs(v,min(e[i].len,maxflow - ans));
e[i].len -= temflow; /// 前向弧流量减少
e[i ^ 1].len += temflow; /// 反向弧流量增加
ans += temflow;
if (ans == maxflow) break;
}
if (!ans) depth[u] = INF;
return ans;
}
int dinic()
{
int ans = 0;
while(bfs()){
ans += dfs(se,INF);
}
return ans;
}
int main()
{
int t;
scanf("%d",&t);
while(t--){
int n,m;
scanf("%d %d",&n,&m);
memset(head,-1,sizeof(head));
cnt = 0;
for (int i = 0; i < m; i++){
scanf("%d %d %d",&s[i].u,&s[i].v,&s[i].len);
add(s[i].u,s[i].v,s[i].len);
}
scanf("%d %d",&se,&ed);
dijkstra();
memset(head,-1,sizeof(head));
cnt = 0;
for (int i = 0; i < m; i++){
if (lowcost[s[i].v] == lowcost[s[i].u] + s[i].len){
add(s[i].u,s[i].v,1);
add(s[i].v,s[i].u,0); /// 反向弧为0
}
}
printf("%d\n",dinic());
}
return 0;
}
HDU 3338 题目链接
题意
有黑方块和白方块,白方块用来填写数字,黑方块有左下和右上两个三角形,两个三角形都有一个数,左下代表从这块以下到第二个黑方块中的所有白方块数字之和,右上同理,代表的是一行的白方块数字之和