zoukankan      html  css  js  c++  java
  • 网络流24题

    汽车加油行驶问题

    餐巾计划问题

    建立两排点, 第一排每个点表示第 (i) 天的旧毛巾, 第二排每个点表示第 (i) 天的新毛巾, 新毛巾有三种来源, 旧毛巾有三种去处

    • 从虚拟源点向第 (i) 天的旧毛巾连一条容量为 (r_i) 费用为 (0) 的边
    • 从第 (i) 天的新毛巾向虚拟汇点连一条容量为 (r_i) 费用为 (0) 的边
    • 从第 (i) 天的旧毛巾向第 (i + 1) 天的旧毛巾连一条容量为 (+infty) 费用为 (0) 的边
    • 从第 (i) 天的旧毛巾向第 (i + m) 天的新毛巾连一条容量为 (+infty) 费用为 (f) 的边
    • 从第 (i) 天的旧毛巾向第 (i + n) 天的新毛巾连一条容量为 (+infty) 费用为 (s) 的边
    • 从虚拟源点向第 (i) 天的新毛巾连一条容量为 (+infty) 费用为 (p) 的边

    使用计划和最大可行流一一对应, 求最小费用最大流即可

    #include <bits/stdc++.h>
    using namespace std;
    
    const int N = 800 * 2 + 10;
    const int M = (800 * 6 + 10) * 2;
    const int INF = 1e9;
    
    int n, p, x, xp, y, yp, S, T;
    struct Edge
    {
    	int to, nxt, flow, w; 
    }line[M];
    int fist[N], idx;
    int d[N], pre[N], incf[N];
    bool st[N];
    
    void add(int x, int y, int z, int w)
    {
    	line[idx] = {y, fist[x], z, w};
    	fist[x] = idx ++;
    	line[idx] = {x, fist[y], 0, -w};
    	fist[y] = idx ++;
    }
    
    bool spfa()
    {
    	queue<int> q;
    	memset(d, 0x3f, sizeof d);
    	memset(incf, 0, sizeof incf);
    	q.push(S), d[S] = 0, incf[S] = INF;
    	while(!q.empty())
    	{
    		int u = q.front(); q.pop();
    		st[u] = 0;
    		for(int i = fist[u]; i != -1; i = line[i].nxt)
    		{
    			int v = line[i].to;
    			if(line[i].flow && d[v] > d[u] + line[i].w)
    			{
    				d[v] = d[u] + line[i].w;
    				pre[v] = i;
    				incf[v] = min(line[i].flow, incf[u]);
    				if(!st[v])
    				{
    					q.push(v);
    					st[v] = 1;
    				}
    			}
    		}
    	}
    	return incf[T] > 0;
    }
    
    int EK()
    {
    	int cost = 0;
    	while(spfa())
    	{
    		int t = incf[T];
    		cost += t * d[T];
    		for(int i = T; i != S; i = line[pre[i] ^ 1].to)
    		{
    			line[pre[i]].flow -= t;
    			line[pre[i] ^ 1].flow += t;
    		}
    	}
    	return cost;
    }
    
    int main()
    {
    	scanf("%d%d%d%d%d%d", &n, &p, &x, &xp, &y, &yp);
    	S = 0, T = n * 2 + 1;
    	memset(fist, -1, sizeof fist);
    	for(int i = 1; i <= n; ++ i)
    	{
    		int r;
    		scanf("%d", &r);
    		add(S, i, r, 0);
    		add(i + n, T, r, 0);
    		add(S, i + n, INF, p);
    		if(i < n) add(i, i + 1, INF, 0);
    		if(i + x <= n) add(i, i + x + n, INF, xp);
    		if(i + y <= n) add(i, i + y + n, INF, yp);
    	}
    	
    	printf("%d
    ", EK());
    	return 0;
    }
    

    太空飞行计划问题

    当所需的器材都被选时,某实验才可被选择,该问题是一个最大权闭合子图问题

    • 从源点向所有实验连一条容量为该实验收益的边
    • 从所有器材向汇点连一条容量为该器材花费的边
    • 从每个实验向所需的器材连一条容量为 (+infty) 的边

    答案即为 (W(V^+)) - 最小割

    #include <bits/stdc++.h>
    using namespace std;
    
    const int N = 100 + 10;
    const int M = (50 * 50 + N + 10) * 2;
    const int INF = 1e9;
    
    int n, m, S, T;
    struct Edge
    {
    	int to, nxt, flow;
    }line[M];
    int fist[N], idx;
    int cur[N], d[N];
    bool st[N];
    
    void add(int x, int y, int z)
    {
    	line[idx] = {y, fist[x], z};
    	fist[x] = idx ++;
    	line[idx] = {x, fist[y], 0};
    	fist[y] = idx ++;	
    }
    
    bool bfs()
    {
    	queue<int> q;
    	memset(d, -1, sizeof d);
    	q.push(S), d[S] = 0, cur[S] = fist[S]; 
    	while(!q.empty())
    	{
    		int u = q.front(); q.pop();
    		for(int i = fist[u]; i != -1; i = line[i].nxt)
    		{
    			int v = line[i].to;
    			if(d[v] == -1 && line[i].flow)
    			{
    				d[v] = d[u] + 1;
    				cur[v] = fist[v];
    				if(v == T) return 1;
    				q.push(v); 
    			}
    		}
    	}
    	return 0;
    }
    
    int find(int u, int limit)
    {
    	if(u == T) return limit;
    	int flow = 0;
    	for(int i = cur[u]; i != -1 && flow < limit; i = line[i].nxt)
    	{
    		cur[u] = i;
    		int v = line[i].to;
    		if(d[v] == d[u] + 1 && line[i].flow)
    		{
    			int t = find(v, min(line[i].flow, limit - flow));
    			if(!t) d[v] = -1;
    			line[i].flow -= t;
    			line[i ^ 1].flow += t;
    			flow += t;
    		}
    	}
    	return flow;
    }
    
    int dinic()
    {
    	int res = 0, flow;
    	while(bfs()) while(flow = find(S, INF)) res += flow;
    	return res;
    }
    
    void dfs(int u)
    {
    	st[u] = 1;
    	for(int i = fist[u]; i != -1; i = line[i].nxt)
    	{
    		int v = line[i].to;
    		if(!st[v] && line[i].flow)
    			dfs(v);
    	}
    }
    
    int main()
    {
    	scanf("%d%d", &m, &n);
    	S = 0, T = m + n + 1;
    	memset(fist, -1, sizeof fist);
    	
    	int tot = 0;
    	getchar();
    	for(int i = 1; i <= m; ++ i)
    	{
    		int w, id;
    		string line;
    		getline(cin, line);
    		stringstream ssin(line);
    		ssin >> w;
    		add(S, i, w);
    		while(ssin >> id) add(i, m + id, INF);
    		tot += w;
    	}
    	for(int i = 1; i <= n; ++ i)
    	{
    		int p;
    		scanf("%d", &p);
    		add(i + m, T, p);
    	}
    	
    	int res = dinic();
    	dfs(S);
    	
    	for(int i = 1; i <= m; ++ i) 
    		if(st[i]) printf("%d ", i); 
    	puts("");
    	for(int i = 1; i <= n; ++ i)
    		if(st[i + m]) printf("%d ", i);
    	puts("");
    	printf("%d
    ", tot - res);
    	return 0;
    }
    

    航空路线问题

    最小路径覆盖问题

    试题库问题

    负载平衡问题

    把货物当成流,仓库分为两类, 第一类的数量大于平均值, 第二类的数量小于平均值, 相邻的两个仓库之间可以运输货物

    • 从源点向所有第一类仓库连一条容量为 (a_i - ar{x}) 费用为 (0) 的边
    • 从所有第二类仓库向汇点连一条容量为 (ar{x} - a_i) 费用为 (0) 的边
    • 从每个仓库向相邻两个仓库之间连一条容量为 (+infty) 费用为 (1) 的边

    每个运输方案和最大可行流一一对应, 求最小费用最大流即可

    #include <bits/stdc++.h>
    using namespace std;
    
    const int N = 100 + 10;
    const int M = (N * 3 + 10) * 2;
    const int INF = 1e9;
    
    int n, m, S, T;
    int s[N];
    struct Edge
    {
    	int to, nxt, flow, w;
    }line[M];
    int fist[N], idx;
    int d[N], pre[N], incf[N];
    bool st[N];
    
    void add(int x, int y, int z, int w)
    {
    	line[idx] = {y, fist[x], z, w};
    	fist[x] = idx ++;
    	line[idx] = {x, fist[y], 0, -w};
    	fist[y] = idx ++; 	
    }
    
    bool spfa()
    {
    	queue<int> q;
    	memset(d, 0x3f, sizeof d);
    	memset(incf, 0, sizeof incf);
    	q.push(S), d[S] = 0, incf[S] = INF;
    	while(!q.empty())
    	{
    		int u = q.front(); q.pop();
    		st[u] = 0;
    		for(int i = fist[u]; i != -1; i = line[i].nxt)
    		{
    			int v = line[i].to;
    			if(line[i].flow && d[v] > d[u] + line[i].w)
    			{
    				d[v] = d[u] + line[i].w;
    				pre[v] = i;
    				incf[v] = min(line[i].flow, incf[u]);
    				if(!st[v])
    				{
    					q.push(v);
    					st[v] = 1;
    				}
    			}
    		}
    	}	
    	return incf[T] > 0;
    }
    
    int EK()
    {
    	int cost = 0;
    	while(spfa())	
    	{
    		int t = incf[T];
    		cost += t * d[T];
    		for(int i = T; i != S; i = line[pre[i] ^ 1].to)
    		{
    			line[pre[i]].flow -= t;
    			line[pre[i] ^ 1].flow += t;
    		}
    	}
    	return cost;
    }
    
    int main()
    {
    	scanf("%d", &n);
    	S = 0, T = n + 1;
    	memset(fist, -1, sizeof fist);
    	int tot = 0;
    	for(int i = 1; i <= n; ++ i)
    	{
    		scanf("%d", &s[i]);
    		tot += s[i];
    		add(i, i < n ? i + 1 : 1, INF, 1);
    		add(i, i > 1 ? i - 1 : n, INF, 1);
    	}	
    	tot /= n;
    	for(int i = 1; i <= n; ++ i)
    		if(s[i] > tot) add(S, i, s[i] - tot, 0);
    		else if(s[i] < tot) add(i, T, tot - s[i], 0);
    	
    	printf("%d
    ", EK());
    	return 0;
    } 
    

    运输问题

    把货物看成流,从仓库流向商店,有多个源汇点,可以建立一个虚拟源点和虚拟汇点

    • 从源点向每个仓库连一条容量为 (a_i) 费用为 (0) 的边
    • 从每个商店向汇点连一条容量为 (b_i) 费用为 (0) 的边
    • 从每个商店向每个仓库连一条容量为 (+infty) 费用为 (c_{i,j}) 的边

    每个运输方案和最大可行流一一对应,求最小费用最大流和最大费用最大流即可

    #include <bits/stdc++.h> 
    using namespace std;
    
    const int N = 100 + 50 + 10;
    const int M = (N + 100 * 50 + 10) * 2;
    const int INF = 1e9;
    
    int n, m, S, T;
    struct Edge
    {
    	int to, nxt, flow, w;
    }line[M];
    int fist[N], idx;
    int d[N], incf[N], pre[N];
    bool st[N];
    
    void add(int x, int y, int z, int w)
    {
    	line[idx] = {y, fist[x], z, w};
    	fist[x] = idx ++;
    	line[idx] = {x, fist[y], 0, -w};
    	fist[y] = idx ++; 
    }
    
    bool spfa()
    {
    	queue<int> q;
    	memset(d, 0x3f, sizeof d);
    	memset(incf, 0, sizeof incf);
    	q.push(S), d[S] = 0, incf[S] = INF;
    	while(!q.empty())
    	{
    		int u = q.front(); q.pop();
    		st[u] = 0;
    		for(int i = fist[u]; i != -1; i = line[i].nxt)
    		{
    			int v = line[i].to;
    			if(line[i].flow && d[v] > d[u] + line[i].w)
    			{
    				d[v] = d[u] + line[i].w;
    				pre[v] = i;
    				incf[v] = min(line[i].flow, incf[u]);
    				if(!st[v])
    				{
    					q.push(v);
    					st[v] = 1; 
    				} 
    			}
    		}
    	}
    	return incf[T] > 0; 
    }
    
    int EK()
    {
    	int cost = 0;
    	while(spfa())
    	{
    		int t = incf[T]; 
    		cost += t * d[T];
    		for(int i = T; i != S; i = line[pre[i] ^ 1].to)
    		{
    			line[pre[i]].flow -= t;
    			line[pre[i] ^ 1].flow += t;
    		}
    	}
    	return cost;
    }
    
    int main()
    {
    	scanf("%d%d", &m, &n);
    	S = 0, T = n + m + 1;
    	memset(fist, -1, sizeof fist);
    	for(int i = 1; i <= m; ++ i)
    	{
    		int a;
    		scanf("%d", &a);
    		add(S, i, a, 0);
    	}
    	for(int i = 1; i <= n; ++ i)
    	{
    		int a;
    		scanf("%d", &a);
    		add(i + m, T, a, 0); 
    	}
    	for(int i = 1; i <= m; ++ i)
    		for(int j = 1; j <= n; ++ j)
    		{
    			int a;
    			scanf("%d", &a);
    			add(i, j + m, INF, a);
    		}
    	
    	printf("%d
    ", EK());
    	
    	for(int i = 0; i < idx; i += 2)
    	{
    		line[i].flow += line[i ^ 1].flow;
    		line[i ^ 1].flow = 0;
    		line[i].w = -line[i].w;
    		line[i ^ 1].w = -line[i ^ 1].w;
    	}
    	printf("%d
    ", -EK());
    	
    	return 0;
    }
    

    魔术球问题

    最长不下降子序列问题

    第一问: 用 (dp) 做一个最长不下降子序列.

    第二问:

    • 从源点向所有 (f(i) = 1) 的点连一条容量为 (1) 的边
    • 从所有 (f(i) = s) 的点向汇点连一条容量为 (1) 的边
    • (f(i) = f(j) + 1), 则从 (j)(i)连一条容量为 (1) 的边
    • 为了保证每个点只能选择一次,把一个点拆成入点和出点, 从入点向出点连一条容量为 (1) 的边

    第三问:
    (1) 个点和第 (n) 个点可以用无限次
    对第二问的图做如下修改:

    • (1) 个点和第 (n) 个点的入点到出点的边的容量设置为 (+infty)
    • 从源点到第 (1) 个点的边的容量设置为 (+infty)
    • 从第 (n) 个点到汇点的边(如果存在这条边)的容量设置为 (+infty)

    在第二问的残留网络上继续找增广路即可
    注:当 (s = 1) 的时候, 源点到汇点路径上所有边都是 (+infty),需要特判掉

    #include <bits/stdc++.h>
    using namespace std;
    
    const int N = 500 * 2 + 10;
    const int M = (500 * 2 + 500 * 500 + 10) * 2;
    const int INF = 1e9;
    
    int n, S, T;
    struct Edge
    {
    	int to, nxt, flow;
    }line[M];
    int fist[N], idx;
    int cur[N], d[N];
    int f[N], w[N];
    
    void add(int x, int y, int z)
    {
    	line[idx] = {y, fist[x], z};
    	fist[x] = idx ++;
    	line[idx] = {x, fist[y], 0};
    	fist[y] = idx ++;
    } 
    
    bool bfs()
    {
    	queue<int> q;
    	memset(d, -1, sizeof d);
    	q.push(S), d[S] = 0, cur[S] = fist[S];
    	while(!q.empty())
    	{
    		int u = q.front(); q.pop();
    		for(int i = fist[u]; i != -1; i = line[i].nxt)
    		{
    			int v = line[i].to;
    			if(d[v] == -1 && line[i].flow)
    			{
    				d[v] = d[u] + 1;
    				cur[v] = fist[v];
    				if(v == T) return 1;
    				q.push(v); 
    			}
    		}
    	}
    	return 0;
    }
    
    int find(int u, int limit)
    {
    	if(u == T) return limit;
    	int flow = 0;
    	for(int i = cur[u]; i != -1; i = line[i].nxt)
    	{
    		cur[u] = i;
    		int v = line[i].to;
    		if(d[v] == d[u] + 1 && line[i].flow)
    		{
    			int t = find(v, min(line[i].flow, limit - flow));
    			if(!t) d[v] = -1;
    			line[i].flow -= t;
    			line[i ^ 1].flow += t; 
    			flow += t;
    		}
    	}
    	return flow;
    }
    
    int dinic()
    {
    	int res = 0, flow;
    	while(bfs()) while(flow = find(S, INF)) res += flow;
    	return res;	
    }
    
    int main()
    {
    	scanf("%d", &n);
    	S = 0, T = n * 2 + 1;
    	memset(fist, -1, sizeof fist);
    	
    	for(int i = 1; i <= n; ++ i) scanf("%d", &w[i]);
    	int s = 0;
    	for(int i = 1; i <= n; ++ i)
    	{
    		add(i, i + n, 1);
    		f[i] = 1;
    		for(int j = 1; j < i; ++ j)
    			if(w[j] <= w[i]) f[i] = max(f[i], f[j] + 1);
    		for(int j = 1; j < i; ++ j)
    			if(w[j] <= w[i] && f[i] == f[j] + 1)
    				add(n + j, i, 1); 
    		s = max(s, f[i]);
    		if(f[i] == 1) add(S, i, 1);
    	}
    	for(int i = 1; i <= n; ++ i)
    		if(f[i] == s) add(i + n, T, 1);
    		
    	printf("%d
    ", s);
    	if(s == 1) printf("%d
    %d
    ", n, n);
    	else 
    	{
    		int res = dinic();
    		printf("%d
    ", res);
    		for(int i = 0; i < idx; i += 2)
    		{
    			int a = line[i ^ 1].to, b = line[i].to;
    			if(a == S && b == 1) line[i].flow = INF;
    			else if(a == n + n && b == T) line[i].flow = INF;
    			else if(a == 1 && b == n + 1) line[i].flow = INF;
    			else if(a == n && b == n + n) line[i].flow = INF; 
    		}	
    		printf("%d
    ", dinic() + res);
    	}
    	return 0;
    } 
    

    星际转移问题

    最大流判定
    用并查集判断 (0)(n + 1) 是否连通
    判断使用 (day) 天能否将所有人从 (0) 运送到 (n + 1)
    利用分层图来表示天数, 点表示公交站, 边表示公交车, 人表示流量

    • 从源点向第 (0) 天第 (0) 个公交站连一条容量为 (k) 的边
    • 从每一天第 (n + 1) 个公交站向汇点连一条容量为 (+infty) 的边
    • 每个公交站从第 (i) 天向第 (i + 1)天连一条容量为 (+infty) 的边
    • 按照公交车的移动路线和天数连接容量为该公交车最大承载量的边

    枚举天数,每次在原有网络后面新加一排点,可直接在原有网络的残留网络上继续找增广路, 比二分效率更高

    #include <bits/stdc++.h>
    using namespace std;
    
    const int N = 751 * 15 + 10;
    const int M = (N + 750 + 20 * 751 + 10) * 2;
    const int INF = 1e9; 
    
    int n, m, k, S, T;
    struct Edge
    {
    	int to, nxt, flow;
    }line[M];
    int fist[N], idx;
    int cur[N], d[N];
    struct Ship
    {
    	int h, r, id[30];
    }ships[30];
    int fa[30];
    
    int find(int x)
    {
    	if(fa[x] != x) fa[x] = find(fa[x]);
    	return fa[x];
    }
    
    int get(int i, int day)
    {
    	return day * (n + 2) + i;	
    }
    
    void add(int x, int y, int z)
    {
    	line[idx] = {y, fist[x], z};
    	fist[x] = idx ++;
    	line[idx] = {x, fist[y], 0};
    	fist[y] = idx ++;
    }
    
    bool bfs()
    {
    	queue<int> q;
    	memset(d, -1, sizeof d);
    	q.push(S), d[S] = 0, cur[S] = fist[S];
    	while(!q.empty())
    	{
    		int u = q.front(); q.pop();
    		for(int i = fist[u]; i != -1; i = line[i].nxt)
    		{
    			int v = line[i].to;
    			if(d[v] == -1 && line[i].flow)
    			{
    				d[v] = d[u] + 1;
    				cur[v] = fist[v];
    				if(v == T) return 1;
    				q.push(v);
    			}
    		}
    	}	
    	return 0;
    } 
    
    int find(int u, int limit)
    {
    	if(u == T) return limit;
    	int flow = 0;
    	for(int i = cur[u]; i != -1 && flow < limit; i = line[i].nxt)
    	{
    		cur[u] = i;
    		int v = line[i].to;
    		if(d[v] == d[u] + 1 && line[i].flow)
    		{
    			int t = find(v, min(line[i].flow, limit - flow));
    			if(!t) d[v] = -1;
    			line[i].flow -= t;
    			line[i ^ 1].flow += t;
    			flow += t;
    		}
    	}
    	return flow;
    }
    
    int dinic()
    {
    	int res = 0, flow;
    	while(bfs()) while(flow = find(S, INF)) res += flow;
    	return res;	
    }
    
    int main()
    {
    	scanf("%d%d%d", &n, &m, &k);
    	S = N - 2, T = N - 1;
    	memset(fist, -1, sizeof fist);
    	for(int i = 0; i < 30; ++ i) fa[i] = i;
    	for(int i = 0; i < m; ++ i)
    	{
    		int a, b;
    		scanf("%d%d", &a, &b);
    		ships[i] = {a, b};
    		for(int j = 0; j < b; ++ j)
    		{
    			int id;
    			scanf("%d", &id);
    			if(id == -1) id = n + 1; 
    			ships[i].id[j] = id;
    			if(j) 
    			{
    				int x = ships[i].id[j - 1];
    				fa[find(x)] = find(id);
    			}
    		}
    	}
    	if(find(0) != find(n + 1)) puts("0");
    	else
    	{
    		add(S, get(0, 0), k);
    		add(get(n + 1, 0), T, INF);
    		int day = 1, res = 0;
    		while(1)
    		{
    			add(get(n + 1, day), T, INF);
    			for(int i = 0; i <= n + 1; ++ i)
    				add(get(i, day - 1), get(i, day), INF);
    			for(int i = 0; i < m; ++ i)
    			{
    				int r = ships[i].r;
    				int a = ships[i].id[(day - 1) % r];
    				int b = ships[i].id[day % r];
    				add(get(a, day - 1), get(b, day), ships[i].h);
    			} 
    			res += dinic();
    			if(res >= k) break;
    			++ day;
    		}
    		printf("%d
    ", day);
    	}
    	return 0;
    }
    

    方格取数问题

    飞行员配对方案问题

    二分图最大匹配
    (O(msqrt n))

    • 从源点向第一个点集每个点连一条容量为(1)的边
    • 从第二个点集每个点向汇点连一条容量为(1)的边
    • 两个点集之间可以配对的点连一条容量为(1)的边
    #include <bits/stdc++.h>
    using namespace std;
    
    const int N = 200 + 10;
    const int M = 5200 + 10;
    const int INF = 1e9;
    
    int n, m, S, T;
    struct Edge
    {
    	int to, nxt, flow;
    }line[M];
    int fist[N], idx;
    int cur[N], d[N];
    
    void add(int x, int y, int z)
    {
    	line[idx] = {y, fist[x], z};
    	fist[x] = idx ++;
    	line[idx] = {x, fist[y], 0};
    	fist[y] = idx ++; 
    }
    
    bool bfs()
    {
    	queue<int> q;
    	memset(d, -1, sizeof d);
    	q.push(S), d[S] = 0, cur[S] = fist[S]; 
    	while(!q.empty())
    	{ 
    		int u = q.front(); q.pop();
    		for(int i = fist[u]; i != -1; i = line[i].nxt)
    		{
    			int v = line[i].to;
    			if(d[v] == -1 && line[i].flow)
    			{
    				d[v] = d[u] + 1;
    				cur[v] = fist[v];
    				if(v == T) return 1;
    				q.push(v); 
    			} 
    		}
    	}
    	return 0;
    }
    
    int find(int u, int limit)
    {
    	if(u == T) return limit;
    	int flow = 0;
    	for(int i = cur[u]; i != -1 && flow < limit; i = line[i].nxt)
    	{
    		cur[u] = i;
    		int v = line[i].to;
    		if(d[v] == d[u] + 1 && line[i].flow)
    		{
    			int t = find(v, min(line[i].flow, limit - flow));
    			if(!t) d[v] = -1;
    			line[i].flow -= t;
    			line[i ^ 1].flow += t;
    			flow += t;
    		} 
    	}
    	return flow;
    }
    
    int dinic()
    {
    	int res = 0, flow;
    	while(bfs()) while(flow = find(S, INF)) res += flow;
    	return res;	
    }
    
    int main()
    {
    	scanf("%d%d", &m, &n);
    	memset(fist, -1, sizeof fist);
    	S = 0, T = n + 1;
    	for(int i = 1; i <= m; ++ i) add(S, i, 1);
    	for(int i = m + 1; i <= n; ++ i) add(i, T, 1);
    	int a, b;
    	while(scanf("%d%d", &a, &b) == 2)
    	{
    		if(a == -1 && b == -1) break;
    		add(a, b, 1);
    	}
    	printf("%d
    ", dinic());
    	
    	for(int i = 0; i < idx; i += 2)
    	{
    		int u = line[i ^ 1].to, v = line[i].to;
    		if(v > m && v <= n && !line[i].flow)
    			printf("%d %d
    ", u, v);
    	}
    	return 0;
    } 
    

    骑士共存问题

    如果两个位置放置骑士可以相互攻击到, 则这两个位置之间连一条边
    定义横纵坐标之和为偶数的点为偶点, 横纵坐标之和为奇数的点为奇点, 边一定在偶点和奇点之间, 所以该图是一个二分图
    则该问题转化为最大点权独立集问题, 点权为 (1)

    • 从源点向所有偶点连一条容量为 (1) 的边
    • 从所有奇点向汇点连一条容量为 (1) 的边
    • 相互攻击到的点之间连一条容量为 (+infty) 的边

    答案为 总点权和 - 最小点权覆盖集

    #include <bits/stdc++.h>
    using namespace std;
    
    const int N = 40000 + 10;
    const int M = (N + 20000 * 8 + 10) * 2;
    const int INF = 1e9;
    const int dx[] = {2, -2, 2, -2, 1, -1, 1, -1};
    const int dy[] = {1, 1, -1, -1, 2, 2, -2, -2}; 
    
    int n, m, S, T;
    struct Edge
    {
    	int to, nxt, flow;
    }line[M];
    int fist[N], idx;
    int cur[N], d[N];
    bool g[220][220];
    
    void add(int x, int y, int z)
    {
    	line[idx] = {y, fist[x], z};
    	fist[x] = idx ++;
    	line[idx] = {x, fist[y], 0};
    	fist[y] = idx ++;
    }
    
    bool bfs()
    {
    	queue<int> q;
    	memset(d, -1, sizeof d);
    	q.push(S), d[S] = 0, cur[S] = fist[S];
    	while(!q.empty())
    	{
    		int u = q.front(); q.pop();
    		for(int i = fist[u]; i != -1; i = line[i].nxt)
    		{
    			int v = line[i].to;
    			if(d[v] == -1 && line[i].flow)
    			{
    				d[v] = d[u] + 1;
    				cur[v] = fist[v];
    				if(v == T) return 1;
    				q.push(v);
    			}
    		}
    	} 
    	return 0;
    }
    
    int find(int u, int limit)
    {
    	if(u == T) return limit;
    	int flow = 0;
    	for(int i = cur[u]; i != -1 && flow < limit; i = line[i].nxt)
    	{
    		cur[u] = i;
    		int v = line[i].to;
    		if(d[v] == d[u] + 1 && line[i].flow)
    		{
    			int t = find(v, min(line[i].flow, limit - flow));
    			if(!t) d[v] = -1;
    			line[i].flow -= t;
    			line[i ^ 1].flow += t;
    			flow += t;
    		}
    	}
    	return flow;
    } 
    
    int dinic()
    {
    	int res = 0, flow;
    	while(bfs()) while(flow = find(S, INF)) res += flow;
    	return res;
    }
    
    int get(int x, int y)
    {
    	return (x - 1) * n + y;
    }
    
    int main()
    {
    	scanf("%d%d", &n, &m);
    	S = 0, T = n * n + 1;
    	memset(fist, -1, sizeof fist);
    	for(int i = 1; i <= m; ++ i)
    	{
    		int a, b;
    		scanf("%d%d", &a, &b);
    		g[a][b] = 1;
    	}
    	int tot = 0;
    	for(int i = 1; i <= n; ++ i)
    		for(int j = 1; j <= n; ++ j)
    		{
    			if(g[i][j]) continue;
    			if(i + j & 1) 
    			{
    				add(S, get(i, j), 1);
    				for(int k = 0; k < 8; ++ k)
    				{
    					int x = i + dx[k], y = j + dy[k];
    					if(x >= 1 && x <= n && y >= 1 && y <= n && !g[x][y])
    						add(get(i, j), get(x, y), INF);
    				}
    			}	
    			else add(get(i, j), T, 1);
    			tot ++;
    		}
    	printf("%d
    ", tot - dinic());
    	return 0;
    }
    

    最长k可重线段集问题

    最长k可重区间集问题

    圆桌问题

    二分图多重匹配
    找一个满流的方案,看一下最大流是否满流即可

    • 第一个点集每个点向第二个点集每个点连一条容量为(1)的边
    • 从源点向第一个点集每个点连一条容量为(r_i)的边
    • 从第二个点集每个点向汇点连一条容量为(c_i)的边
    #include <bits/stdc++.h>
    using namespace std;
    
    const int N = 420 + 10;
    const int M = (150 * 270 + N) * 2;
    const int INF = 1e9;
    
    int n, m, S, T;
    struct Edge
    {
    	int to, nxt, flow;
    }line[M];
    int fist[N], idx;
    int cur[N], d[N];
    int r[N], c[N];
    
    void add(int x, int y, int z)
    {
    	line[idx] = {y, fist[x], z};
    	fist[x] = idx ++;
    	line[idx] = {x, fist[y], 0};
    	fist[y] = idx ++;
    }
    
    bool bfs()
    {
    	queue<int> q;
    	memset(d, -1, sizeof d);
    	q.push(S), d[S] = 0, cur[S] = fist[S];
    	while(!q.empty())
    	{
    		int u = q.front(); q.pop();
    		for(int i = fist[u]; i != -1; i = line[i].nxt)
    		{
    			int v = line[i].to;
    			if(d[v] == -1 && line[i].flow)
    			{
    				d[v] = d[u] + 1;
    				cur[v] = fist[v];
    				if(v == T) return 1;
    				q.push(v);
    			}
    		} 
    	} 
    	return 0;
    }
    
    int find(int u, int limit)
    {
    	if(u == T) return limit;
    	int flow = 0;
    	for(int i = cur[u]; i != -1 && flow < limit; i = line[i].nxt)
    	{
    		cur[u] = i;
    		int v = line[i].to;
    		if(d[v] == d[u] + 1 && line[i].flow)
    		{
    			int t = find(v, min(line[i].flow, limit - flow));
    			if(!t) d[v] = -1;
    			line[i].flow -= t;
    			line[i ^ 1].flow += t;
    			flow += t;
    		} 
    	}
    	return flow;
    }
    
    int dinic()
    {
    	int res = 0, flow;
    	while(bfs()) while(flow = find(S, INF)) res += flow;
    	return res;	
    } 
    
    int main()
    {
    	scanf("%d%d", &m, &n);
    	memset(fist, -1, sizeof fist);
    	S = 0, T = n + m + 1;
    	int tot = 0;
    	for(int i = 1; i <= m; ++ i) 
    	{
    		int r;
    		scanf("%d", &r);
    		add(S, i, r);
    		tot += r; 
    	}
    	for(int i = 1; i <= n; ++ i) 
    	{
    		int c;
    		scanf("%d", &c);
    		add(i + m, T, c);
    	}
    	for(int i = 1; i <= m; ++ i)
    		for(int j = 1; j <= n; ++ j)
    			add(i, j + m, 1); 
    			
    	if(dinic() != tot) puts("0");
    	else 
    	{
    		puts("1");
    		for(int i = 1; i <= m; ++ i)
    		{
    			for(int j = fist[i]; j != -1; j = line[j].nxt)
    			{
    				int v = line[j].to;
    				if(v > m && v <= n + m && !line[j].flow) 
    					printf("%d ", v - m);
    			}
    			puts("");
    		}
    	}
    	return 0;
    } 
    

    数字梯形问题

    当路径互不相交时,每个点和边都只能选择一次, 限制在边和点上,对于边上的限制可以直接加到容量上,对于点上的限制考虑拆点, 费用在点上, 加在出点和入点的边上

    • 从每个点的入点到出点连一条容量为 (1) 费用为点权的边
    • 从当前点到下一层可到达的点连一条容量为 (1) 费用为 (0) 的边
    • 从虚拟源点向第一层每个点连一条容量为 (1) 费用为 (0) 的边
    • 从最后一层每个点向虚拟汇点连一条容量为 (1) 费用为 (0) 的边

    当可以在节点处相交时,只对边有限制,把点的限制放开即可,即每个点从入点到出点的流量设为 (+infty),同时最后一层点到虚拟汇点的边的容量限制也要放开

    当允许在结点相交或边相交时,对边和点都没有限制,把边的限制也放开即可,即边的容量设置为 (+infty)

    每种路径方案和最大可行流一一对应,求最大费用最大流即可

    #include <bits/stdc++.h>
    using namespace std;
    
    const int N = 600 * 2 + 10;
    const int M = (600 + 20 + 39 + 600 * 2 + 10) * 2;
    const int INF = 1e9;
    
    int n, m, S, T;
    struct Edge
    {
    	int to, nxt, flow, w;
    }line[M];
    int fist[N], idx;
    int d[N], pre[N], incf[N];
    bool st[N];
    int id[40][40], g[40][40];
    
    void add(int x, int y, int z, int w)
    {
    	line[idx] = {y, fist[x], z, w};
    	fist[x] = idx ++;
    	line[idx] = {x, fist[y], 0, -w};
    	fist[y] = idx ++;	
    }
    
    bool spfa()
    {
    	queue<int> q;
    	memset(d, -0x3f, sizeof d);
    	memset(incf, 0, sizeof incf);
    	q.push(S), d[S] = 0, incf[S] = INF;
    	while(!q.empty())
    	{
    		int u = q.front(); q.pop();
    		st[u] = 0;
    		for(int i = fist[u]; i != -1; i = line[i].nxt)
    		{
    			int v = line[i].to;
    			if(line[i].flow && d[v] < d[u] + line[i].w)
    			{
    				d[v] = d[u] + line[i].w;
    				pre[v] = i;
    				incf[v] = min(line[i].flow, incf[u]);
    				if(!st[v])
    				{
    					q.push(v);
    					st[v] = 1;
    				}
    			}
    		} 
    	} 
    	return incf[T] > 0;
    }
    
    int EK()
    {
    	int cost = 0;
    	while(spfa())
    	{
    		int t = incf[T];
    		cost += t * d[T];
    		for(int i = T; i != S; i = line[pre[i] ^ 1].to)
    		{
    			line[pre[i]].flow -= t;
    			line[pre[i] ^ 1].flow += t;
    		}
    	}
    	return cost;
    }
    
    int main()
    {
    	int cnt = 0;
    	scanf("%d%d", &m, &n);
    	S = ++ cnt; T = ++ cnt;
    	for(int i = 1; i <= n; ++ i)
    		for(int j = 1; j <= m + i - 1; ++ j)
    		{
    			scanf("%d", &g[i][j]);
    			id[i][j] = ++ cnt;
    		}	
    	//
    	memset(fist, -1, sizeof fist); idx = 0;
    	for(int i = 1; i <= n; ++ i)
    		for(int j = 1; j <= m + i - 1; ++ j)
    		{
    			add(id[i][j] * 2, id[i][j] * 2 + 1, 1, g[i][j]);
    			if(i == 1) add(S, id[i][j] * 2, 1, 0);
    			if(i == n) add(id[i][j] * 2 + 1, T, 1, 0);
    			if(i < n) 
    			{
    				add(id[i][j] * 2 + 1, id[i + 1][j] * 2, 1, 0);
    				add(id[i][j] * 2 + 1, id[i + 1][j + 1] * 2, 1, 0);
    			}
    		}
    	printf("%d
    ", EK());
    	//
    	memset(fist, -1, sizeof fist); idx = 0;
    	for(int i = 1; i <= n; ++ i)
    		for(int j = 1; j <= m + i - 1; ++ j)
    		{
    			add(id[i][j] * 2, id[i][j] * 2 + 1, INF, g[i][j]);
    			if(i == 1) add(S, id[i][j] * 2, 1, 0);
    			if(i == n) add(id[i][j] * 2 + 1, T, INF, 0);
    			if(i < n) 
    			{
    				add(id[i][j] * 2 + 1, id[i + 1][j] * 2, 1, 0);
    				add(id[i][j] * 2 + 1, id[i + 1][j + 1] * 2, 1, 0);
    			}
    		}
    	printf("%d
    ", EK());
    	//
    	memset(fist, -1, sizeof fist); idx = 0;
    	for(int i = 1; i <= n; ++ i)
    		for(int j = 1; j <= m + i - 1; ++ j)
    		{
    			add(id[i][j] * 2, id[i][j] * 2 + 1, INF, g[i][j]);
    			if(i == 1) add(S, id[i][j] * 2, 1, 0);
    			if(i == n) add(id[i][j] * 2 + 1, T, INF, 0);
    			if(i < n) 
    			{
    				add(id[i][j] * 2 + 1, id[i + 1][j] * 2, INF, 0);
    				add(id[i][j] * 2 + 1, id[i + 1][j + 1] * 2, INF, 0);
    			}
    		}
    	printf("%d
    ", EK());
    	
    	return 0;
    } 
    

    深海机器人问题

    网格图费用流
    费用在边上,不需要拆点,但是费用只能取一次,所以两个点之间要建两种边

    • 从虚拟源点向每个起点连一条容量为 (a_i) 费用为 (0) 的边
    • 从每个终点向虚拟汇点连一条容量为 (b_i) 费用为 (0) 的边
    • 可到达的点之间连一条容量为 (1) 费用为 (c_i) 的边
    • 可到达的点之间连一条容量为 (+infty) 费用为 (0) 的边

    方案和最大可行流一一对应, 求最大费用最大流即可

    #include <bits/stdc++.h>
    using namespace std;
    
    const int N = 16 * 16 + 10;
    const int M = (N * 2 * 2 + 4 + 6 + 10) * 2;
    const int INF = 1e9;
    
    int n, m, S, T;
    struct Edge
    {
    	int to, nxt, flow, w;
    }line[M];
    int fist[N], idx;
    int d[N], pre[N], incf[N];
    bool st[N];
    
    void add(int x, int y, int z, int w)
    {
    	line[idx] = {y, fist[x], z, w};
    	fist[x] = idx ++;
    	line[idx] = {x, fist[y], 0, -w};
    	fist[y] = idx ++;
    }
    
    bool spfa()
    {
    	queue<int> q;
    	memset(d, -0x3f, sizeof d);
    	memset(incf, 0, sizeof incf);
    	q.push(S), d[S] = 0, incf[S] = INF;
    	while(!q.empty())
    	{
    		int u = q.front(); q.pop();
    		st[u] = 0;
    		for(int i = fist[u]; i != -1; i = line[i].nxt)
    		{
    			int v = line[i].to;
    			if(line[i].flow && d[v] < d[u] + line[i].w)
    			{
    				d[v] = d[u] + line[i].w;
    				pre[v] = i;
    				incf[v] = min(line[i].flow, incf[u]);
    				if(!st[v])
    				{
    					q.push(v);
    					st[v] = 1;
    				}
    			}
    		}
    	}
    	return incf[T] > 0;
    }
    
    int EK()
    {
    	int cost = 0;
    	while(spfa())
    	{
    		int t = incf[T];
    		cost += t * d[T];
    		for(int i = T; i != S; i = line[pre[i] ^ 1].to)
    		{
    			line[pre[i]].flow -= t;
    			line[pre[i] ^ 1].flow += t;
    		}
    	}
    	return cost;
    }
    
    int get(int x, int y)
    {
    	return x * (m + 1) + y;
    }
     
    int main()
    {
    	int A, B;
    	scanf("%d%d%d%d", &A, &B, &n, &m);
    	S = (n + 1) * (m + 1), T = S + 1;
    	memset(fist, -1, sizeof fist);
    	for(int i = 0; i <= n; ++ i)
    		for(int j = 0; j < m; ++ j)	
    		{
    			int c;
    			scanf("%d", &c);
    			add(get(i, j), get(i, j + 1), 1, c);
    			add(get(i, j), get(i, j + 1), INF, 0);
    		} 
    	for(int i = 0; i <= m; ++ i)
    		for(int j = 0; j < n; ++ j)
    		{
    			int c;
    			scanf("%d", &c);
    			add(get(j, i), get(j + 1, i), 1, c);
    			add(get(j, i), get(j + 1, i), INF, 0);
    		}
    	for(int i = 1; i <= A; ++ i)
    	{
    		int k, x, y;
    		scanf("%d%d%d", &k, &x, &y);
    		add(S, get(x, y), k, 0);
    	}
    	for(int i = 1; i <= B; ++ i)
    	{
    		int r, x, y;
    		scanf("%d%d%d", &r, &x, &y);
    		add(get(x, y), T, r, 0);
    	}
    	printf("%d
    ", EK());
    	return 0;
    } 
    

    软件补丁问题

    火星探险问题

    分配问题

    二分图最优匹配问题, 与二分图匹配的建图方式类似, 建立虚拟源点和虚拟汇点

    • 从源点向每个工作连一条容量为 (1) 费用为 (0) 的边
    • 从每个工人向汇点连一条容量为 (1) 费用为 (0) 的边
    • 从每个工作向每个工人连一条容量为 (1) 费用为 (c_{i,j}) 的边

    最大可行流和分配方案一一对应, 求最小费用最大流和最大费用最大流即可

    #include <bits/stdc++.h>
    using namespace std;
    
    const int N = 100 + 10;
    const int M = (50 * 50 + N + 10) * 2;
    const int INF = 1e9;
    
    int n, m, S, T;
    struct Edge
    {
    	int to, nxt, flow, w;
    }line[M];
    int fist[N], idx;
    int d[N], pre[N], incf[N];
    bool st[N];
    
    void add(int x, int y, int z, int w)
    {
    	line[idx] = {y, fist[x], z, w};
    	fist[x] = idx ++;
    	line[idx] = {x, fist[y], 0, -w};
    	fist[y] = idx ++;
    }
    
    bool spfa()
    {
    	queue<int> q;
    	memset(d, 0x3f, sizeof d);
    	memset(incf, 0, sizeof incf);
    	q.push(S), d[S] = 0, incf[S] = INF;  
    	while(!q.empty())
    	{
    		int u = q.front(); q.pop();
    		st[u] = 0;
    		for(int i = fist[u]; i != -1; i = line[i].nxt)
    		{
    			int v = line[i].to;
    			if(line[i].flow && d[v] > d[u] + line[i].w)
    			{
    				d[v] = d[u] + line[i].w;
    				pre[v] = i;
    				incf[v] = min(line[i].flow, incf[u]);
    				if(!st[v])
    				{
    					q.push(v);
    					st[v] = 1;
    				}
    			}
    		}
    	}
    	return incf[T] > 0;
    }
    
    int EK()
    {
    	int cost = 0;
    	while(spfa())
    	{
    		int t = incf[T];
    		cost += t * d[T];
    		for(int i = T; i != S; i = line[pre[i] ^ 1].to)
    		{
    			line[pre[i]].flow -= t;
    			line[pre[i] ^ 1].flow += t;
    		}
    	}
    	return cost;
    }
    
    int main()
    {
    	scanf("%d", &n);
    	S = 0, T = n * 2 + 1;
    	memset(fist, -1, sizeof fist);
    	for(int i = 1; i <= n; ++ i)
    	{
    		add(S, i, 1, 0);
    		add(i + n, T, 1, 0);
    	}
    	for(int i = 1; i <= n; ++ i)
    		for(int j = 1; j <= n; ++ j)
    		{
    			int c;
    			scanf("%d", &c);
    			add(i, j + n, 1, c);
    		}
    	
    	printf("%d
    ", EK());
    	for(int i = 0; i < idx; i += 2)
    	{
    		line[i].flow += line[i ^ 1].flow;
    		line[i ^ 1].flow = 0;
    		line[i].w = -line[i].w;
    		line[i ^ 1].w = -line[i ^ 1].w;
    	}
    	printf("%d
    ", -EK());
    	return 0;
    } 
    

    机器人路径规划问题

    孤岛营救问题

  • 相关阅读:
    App架构经验总结
    通过C语言程序改动控制台的背景和前景颜色
    java实现读取yaml文件,并获取值
    java 实现yaml 数据转json与map
    yaml标记语言的简介
    重新学习之spring第二个程序,配置AOP面向切面编程
    重新学习之spring第一个程序,配置IOC容器
    重新学习Spring之核心IOC容器的底层原理
    hibernate之xml映射文件关系维护,懒加载,级联
    hibernate映射xml文件配置之一对多,多对多
  • 原文地址:https://www.cnblogs.com/ooctober/p/14411026.html
Copyright © 2011-2022 走看看