一、网络流建模之拆点
1.1问题引入
现有工厂s,仓库t,中转站若干,s到中转站有若干道路,中转站到仓库有道路若干,工厂要向仓库运输一定的货物,每条道路都有最大运输量限制,问最大运货量。
1.2转化为网络流问题
显然上述问题我们可以轻松的建模转化为网络流问题
该流网络的最大流就是问题的解
1.3带容量点的流网络
随着需求量越来越大,中转站工作人员的工作负担也越来越重,因此中转站也做出了中转量限制,问此时的最大运货量
1.4带容量点拆点
我们将每个中转站拆成两个点,一个入点,代表从工厂出发进入中转站,一个出点,代表从中转站出发进入仓库
然后入点和出点之间连边,边的容量为中转量,此时流网络的最大流就是问题的解
1.5拆点的应用
拆点法为我们网络流问题一种常用的建模方式,很多问题都可以抽象为带容量限制点的流网络问题,然后拆点建模求最大流/最小割解决。
二、OJ练习
POJ3281 Dining
原题链接
思路分析
题目讲每种食物或饮料只能被一头牛消耗,然后一头牛也只能选择一种饮料和食物,那么对于这样的条件我们就可以通过把牛拆点来简化问题(如上图)
每头牛拆成一个入点和一个出点,每头牛能吃的食物向该牛的入点连边,每头牛的出点又都向其能喝的饮料连边,然后源点向所有的食物连边,所有饮料向汇点连边,上述所有边的容量都是1
这样我们建立的这个网络的最大流就对应了原问题的最优解,因为最大流的一点流量代表遵守规则的同时一头牛被满足,而又是最大的,所以最大流就是原问题的解。
AC代码
#include <iostream>
#include <cstring>
#include <algorithm>
using namespace std;
const int N = 410, M = (20000 + 300) << 1, inf = 1e9;
int n, F, D;
int s, t, q[N], f, b, d[N], head[N], cur[N], idx;
struct edge{
int v, c, nxt;
}edges[M];
void addedge(int u, int v, int c){
edges[idx].v = v, edges[idx].c = c, edges[idx].nxt = head[u], head[u] = idx++;
}
void add(int u, int v, int c){
addedge(u, v, c), addedge(v, u, 0);
}
bool bfs(){
memset(d, 0, sizeof d), f = b = 0, d[q[b++] = s] = 1;
while(b - f){
int u = q[f++];
for(int i = head[u]; ~i; i = edges[i].nxt){
int v = edges[i].v;
if(!d[v] && edges[i].c){
d[q[b++] = v] = d[u] + 1;
if(v == t) return true;
}
}
}
return false;
}
int dfs(int u, int lim){
if(u == t) return lim;
int res = 0;
for(int i = cur[u]; ~i && lim; i = edges[i].nxt){
cur[u] = i;
int v = edges[i].v;
if(d[v] == d[u] + 1 && edges[i].c){
int incf = dfs(v, min(lim, edges[i].c));
if(!incf) d[v] = 0;
res += incf, lim -= incf, edges[i].c -= incf, edges[i ^ 1].c += incf;
}
}
return res;
}
int dinic(){
int res = 0;
while(bfs())
memcpy(cur, head, sizeof head), res += dfs(s, inf);
return res;
}
int main()
{
//freopen("in.txt", "r", stdin);
ios::sync_with_stdio(false), cin.tie(0), cout.tie(0);
cin >> n >> F >> D, s = 0, t = n * 2 + F + D + 1, memset(head, -1, sizeof head);
for(int i = 1, x, y; i <= n; i++){
cin >> x >> y, add(i, i + n, 1);
for(int j = 0, z; j < x; j++) cin >> z, add(2 * n + z, i, 1);
for(int j = 0, z; j < y; j++) cin >> z, add(i + n, 2 * n + F + z, 1);
}
for(int i = 1; i <= F; i++) add(s, 2 * n + i, 1);
for(int i = 1; i <= D; i++) add(2 * n + F + i, t, 1);
cout << dinic();
return 0;
}
P2766 最长不下降子序列问题
原题链接
P2766 最长不下降子序列问题 - 洛谷 | 计算机科学教育新生态 (luogu.com.cn)
思路分析
-
问题一:直接O(n)动规解决(这里规定状态数组dp[]),答案记为ma
-
问题二:我们把问题抽象成网络流模型
- 每个dp[i]都看成一个节点,然后拆点为入点出点
- 对于dp[i] = 1,从源点向入点连容量为1的边
- 对于dp[i] = ma,从出点向汇点连容量为1的边
- 入点和出点之间连容量为1的边
这样答案就是最大流,因为最大流每一点流量对应了一个最长非下降子序列,容量的设定又保证了路径不交叉
-
问题三:将源点到dp[1]入点的边设置为容量为无穷,dp[1]的入点和出点的边也容量为正无穷,dp[n]和汇点同理,然后在残留网络上再次跑最大流加到上一问的最大流上就是答案
AC代码
#include<iostream>
#include<cstring>
#include<algorithm>
using namespace std;
const int N = 1010, M = (505 * 3 + 505 * 505) << 1, inf = 1e9;
using namespace std;
struct edge{
int v, c, nxt;
}edges[M];
int head[N], cur[N], idx;
int q[N], d[N], f, b, s, t;
int a[505], dp[505], n, ma;
void addedge(int u, int v, int c){
edges[idx] = {v, c, head[u]}, head[u] = idx++;
}
void add(int u, int v, int c){
addedge(u, v, c), addedge(v, u, 0);
}
bool bfs(){
memset(d, 0, sizeof d), b = f = 0, d[q[b ++] = s] = 1;
while(b - f){
int u = q[f++];
for(int i = head[u]; ~i; i = edges[i].nxt){
int v = edges[i].v;
if(!d[v] && edges[i].c){
d[q[b++] = v] = d[u] + 1;
if(v == t) return true;
}
}
}
return false;
}
int dfs(int u, int lim){
if(u == t) return lim;
int res = 0;
for(int i = cur[u], v; ~i && lim; i = edges[i].nxt){
v = edges[i].v;
if(d[v] == d[u] + 1 && edges[i].c){
int incf = dfs(v, min(lim, edges[i].c));
if(!incf) d[v] = 0;
res += incf, edges[i].c -= incf, edges[i^1].c += incf, lim -= incf;
}
}
return res;
}
int dinic(){
int res = 0;
while(bfs())
memcpy(cur, head, sizeof head), res += dfs(s, inf);
return res;
}
int main(){
//freopen("in.txt", "r", stdin);
ios::sync_with_stdio(false), cin.tie(0), cout.tie(0);
cin >> n, s = 0, t = n << 1 | 1, memset(head, -1, sizeof head);
for(int i = 1; i <= n; i++) cin >> a[i];
for(int i = 1; i <= n; i++){
add(i, i + n, 1), dp[i] = 1;
for(int j = 1; j < i; j++) if(a[i] >= a[j]) dp[i] = max(dp[i], dp[j] + 1);
for(int j = 1; j < i; j++) if(a[i] >= a[j] && dp[i] == dp[j] + 1) add(j + n, i, 1);
ma = max(ma, dp[i]);
}
for(int i = 1; i <= n; i++) if(dp[i] == ma) add(i + n, t, 1); else if(dp[i] == 1) add(s, i, 1);
cout << ma << '\n';
if(ma == 1) cout << n << '\n' << n;
else{
int res = dinic();
cout << res << '\n';
for(int i = 0, u, v; i < idx; i++){
u = edges[i ^ 1].v, v = edges[i].v;
if(u == s && v == 1) edges[i].c = inf;
else if(u == 1 && v == n + 1) edges[i].c = inf;
else if(u == n && v == 2 * n) edges[i].c = inf;
else if(u == n * 2 && v == t) edges[i].c = inf;
}
res += dinic(), cout << res;
}
return 0;
}
//task1——lis
//task2——dinic
//task3——inf ——dinic
POJ3498 March of the Penguins
原题链接
3498 – March of the Penguins (poj.org)
思路分析
考虑建立虚拟源点,每个冰块上的企鹅都是从虚拟源点输送过去的,然后可以会面的冰块我们作为汇点
将每个冰块拆点为入点和出点,入点到出点边的容量即最大起跳次数
源点到入点的边的容量为初始企鹅数目
出点到其它距离在最大跳程内冰块的入点的边容量为正无穷
然后枚举每个冰块作为汇点时的最大流,如果最大流是总企鹅数目,那么该冰块就可以作为会面冰块
建模后,源点到汇点的最大流的每一点流量就是若干次跳跃跳到会面冰块上的一个企鹅,并且保证了所有冰块的起跳次数的限制并且保证最大,所以是原问题的解。
AC代码
#include<iostream>
#include<cstring>
#include<algorithm>
using namespace std;
const int N = 210, M = (105 * 105 + 105 * 3) << 1, inf = 1e9;
const double eps = 1e-8;
int head[N], cur[N], idx;
int q[N], d[N], b, f, s, t, tot, ans;
int n;
double dst;
struct pos{
double x, y;
}p[N];
struct edge{
int v, c, nxt;
}edges[M];
void addedge(int u, int v, int c){
edges[idx].v = v, edges[idx].c = c, edges[idx].nxt = head[u], head[u] = idx++;
}
void add(int u, int v, int c){
addedge(u, v, c), addedge(v, u, 0);
}
bool bfs(){
memset(d, 0, sizeof d),f = b = 0, d[q[b++] = s] = 1;
while(b - f){
int u = q[f++];
for(int i = head[u]; ~i; i = edges[i].nxt){
int v = edges[i].v;
if(!d[v] && edges[i].c){
d[q[b++] = v] = d[u] + 1;
if(v == t) return true;
}
}
}
return false;
}
int dfs(int u, int lim){
if(u == t) return lim;
int res = 0;
for(int i = cur[u]; ~i && lim; i = edges[i].nxt){
int v = edges[i].v;
cur[u] = i;
if(d[v] == d[u] + 1 && edges[i].c){
int incf = dfs(v, min(edges[i].c, lim));
if(!incf) d[v] = 0;
lim -= incf, edges[i].c -= incf, edges[i ^ 1].c += incf, res += incf;
}
}
return res;
}
int dinic(){
int res = 0;
while(bfs())
memcpy(cur, head, sizeof head), res += dfs(s, inf);
return res;
}
bool check(double dx, double dy){
return dx * dx + dy * dy < dst * dst + eps;
}
int main(){
//freopen("in.txt", "r", stdin);
ios::sync_with_stdio(false), cin.tie(0), cout.tie(0);
int _ = 1;
cin >> _;
while(_--){
cin >> n >> dst, memset(head, -1, sizeof head), idx = tot = ans = 0;
for(int i = 1, a, b; i <= n; i++)
cin >> p[i].x >> p[i].y >> a >> b, tot += a, add(s, i, a), add(i, i + n, b);
for(int i = 1; i <= n; i++)
for(int j = i + 1; j <= n; j++)
if(check(p[i].x - p[j].x, p[i].y - p[j].y)) add(i + n, j, inf), add(j + n, i, inf);
for(t = 1; t <= n; t++) {
for(int i = 0; i < idx; i += 2) edges[i].c += edges[i ^ 1].c, edges[i ^ 1].c = 0;
if(dinic() == tot) ans++, cout << t - 1 << ' ';
}
ans ? cout << '\n' : cout << "-1\n";
}
return 0;
}
标签:OJ,容量,int,拆点,建模,入点,include,dp
From: https://blog.csdn.net/EQUINOX1/article/details/136752397