网络流24题
一些定理
最小点覆盖 \(=\) 最大匹配
最大独立集 \(= V-\) 最小点覆盖
最小边覆盖 \(=V-\) 最大匹配(对于不存在孤立点的图)
最大流 \(=\) 最小割
模板
最大流
struct Edge
{
int to, next, cap, flow;
}edge[maxm];
int tot;
int head[maxn];
void init()
{
tot = 2;
memset(head, -1, sizeof(head));
}
void build(int u, int v, int w, int rw = 0)
{
edge[tot].to = v; edge[tot].cap = w; edge[tot].flow = 0;
edge[tot].next = head[u]; head[u] = tot++;
edge[tot].to = u; edge[tot].cap = 0; edge[tot].flow = 0;
edge[tot].next = head[v]; head[v] = tot++;
}
int Q[maxn];
int dep[maxn], cur[maxn], sta[maxn];
bool bfs(int s, int t, int n)
{
int front = 0, tail = 0;
memset(dep, -1, sizeof(dep[0]) * (n+1));
dep[s] = 0;
Q[tail++] = s;
while(front < tail)
{
int u = Q[front++];
for (int i = head[u]; i != -1; i = edge[i].next)
{
int v = edge[i].to;
if (edge[i].cap > edge[i].flow && dep[v] == -1)
{
dep[v] = dep[u] + 1;
if (v == t) return true;
Q[tail++] = v;
}
}
}
return false;
}
LL dinic(int s, int t, int n)
{
LL maxflow = 0;
while(bfs(s, t, n))
{
for (int i = 0; i < n; i++) cur[i] = head[i];
int u = s, tail = 0;
while(cur[s] != -1)
{
if (u == t)
{
int tp = inf;
for (int i = tail-1; i >= 0; i--)
tp = min(tp, edge[sta[i]].cap - edge[sta[i]].flow);
//if (tp >= inf) return -1;
maxflow += tp;
for (int i = tail-1; i >= 0; i--)
{
edge[sta[i]].flow += tp;
edge[sta[i]^1].flow -= tp;
if (edge[sta[i]].cap - edge[sta[i]].flow == 0) tail = i;
}
u = edge[sta[tail]^1].to;
}
else if (cur[u] != -1 && edge[cur[u]].cap > edge[cur[u]].flow
&& dep[u]+1 == dep[edge[cur[u]].to])
{
sta[tail++] = cur[u];
u = edge[cur[u]].to;
}
else
{
while(u != s && cur[u] == -1) u = edge[sta[--tail]^1].to;
cur[u] = edge[cur[u]].next;
}
}
}
return maxflow;
}
费用流
struct Edge
{
int to, next, cap, flow, cost;
}edge[maxm];
int tot, N;
int head[maxn], pre[maxn], dis[maxn];
bool vis[maxn];
void init(int n)
{
N = n;
tot = 0;
memset(head, -1, sizeof(head));
}
void build(int u, int v, int w, int cost)
{
edge[tot].to = v; edge[tot].cap = w;
edge[tot].cost = cost; edge[tot].flow = 0;
edge[tot].next = head[u]; head[u] = tot++;
edge[tot].to = u; edge[tot].cap = 0;
edge[tot].cost = -cost; edge[tot].flow = 0;
edge[tot].next = head[v]; head[v] = tot++;
}
bool relax(int x, int y, int i)
{
if (edge[i].cap > edge[i].flow && dis[y] > dis[x] + edge[i].cost)
{
dis[y] = dis[x] + edge[i].cost;
pre[y] = i;
return true;
}
return false;
}
bool SPFA(int s, int t)
{
queue<int> q;
for (int i = 0; i <= N; i++)
dis[i] = inf, vis[i] = false, pre[i] = -1;
dis[s] = 0, q.push(s), vis[s] = true;
while(!q.empty())
{
int x = q.front();
q.pop();
for (int i = head[x]; i != -1; i = edge[i].next)
{
int y = edge[i].to;
if (relax(x, y, i) && !vis[y]) vis[y] = true, q.push(y);
}
vis[x] = false;
}
return pre[t] != -1;
}
LL MinCostMaxflow(int s, int t, LL &cost)
{
LL flow = 0;
cost = 0;
while(SPFA(s, t))
{
int Min = inf;
for (int i = pre[t]; i != -1; i = pre[edge[i^1].to])
Min = min(Min, edge[i].cap - edge[i].flow);
for (int i = pre[t]; i != -1; i = pre[edge[i^1].to])
{
edge[i].flow += Min;
edge[i^1].flow -= Min;
cost += 1ll * edge[i].cost * Min;
}
flow += Min;
}
return flow;
}
题目列表
餐巾计划问题\((solved\) \(at\) \(2019.4.18)\)
飞行员配对方案问题\((solved\) \(at\) \(2019.4.16)\)
太空飞行计划问题\((solved\) \(at\) \(2019.4.18\) \(theoretically)\)
试题库问题\((solved\) \(at\) \(2019.4.16)\)
方格取数问题\((solved\) \(at\) \(2019.4.17)\)
圆桌问题\((solved\) \(at\) \(2019.4.16)\)
分配问题\((solved\) \(at\) \(2019.4.17)\)
运输问题\((solved\) \(at\) \(2019.4.17)\)
餐巾计划问题
把餐巾看成流量,花费看成费用。
把每天拆成早晚,晚上的脏餐巾可以通过洗衣店的费用来向后面的早上连边。
\(S\)向早上连买餐巾的边,向晚上连洗餐巾的边(费用用上面的条件来限制)。
然后把每个早上的点向\(T\)连边就行了,跑费用流。
#include <bits/stdc++.h>
#define FOPI freopen("in.txt", "r", stdin)
#define FOPO freopen("out.txt", "w", stdout)
using namespace std;
typedef long long LL;
const LL inf = 1e14;
const int maxn = 2000 * 2 + 100;
const int maxm = 1e6 + 100;
struct Edge
{
int to, next;
LL cap, flow, cost;
}edge[maxm];
int tot, N;
int head[maxn], pre[maxn];
LL dis[maxn];
bool vis[maxn];
void init(int n)
{
N = n;
tot = 0;
memset(head, -1, sizeof(head));
}
void build(int u, int v, LL w, LL cost)
{
edge[tot].to = v; edge[tot].cap = w;
edge[tot].cost = cost; edge[tot].flow = 0;
edge[tot].next = head[u]; head[u] = tot++;
edge[tot].to = u; edge[tot].cap = 0;
edge[tot].cost = -cost; edge[tot].flow = 0;
edge[tot].next = head[v]; head[v] = tot++;
}
bool relax(int x, int y, int i)
{
if (edge[i].cap > edge[i].flow && dis[y] > dis[x] + edge[i].cost)
{
dis[y] = dis[x] + edge[i].cost;
pre[y] = i;
return true;
}
return false;
}
bool SPFA(int s, int t)
{
queue<int> q;
for (int i = 0; i <= N; i++)
dis[i] = inf, vis[i] = false, pre[i] = -1;
dis[s] = 0, q.push(s), vis[s] = true;
while(!q.empty())
{
int x = q.front();
q.pop();
for (int i = head[x]; i != -1; i = edge[i].next)
{
int y = edge[i].to;
if (relax(x, y, i) && !vis[y]) vis[y] = true, q.push(y);
}
vis[x] = false;
}
return pre[t] != -1;
}
LL MinCostMaxflow(int s, int t, LL &cost)
{
LL flow = 0;
cost = 0;
while(SPFA(s, t))
{
LL Min = inf;
for (int i = pre[t]; i != -1; i = pre[edge[i^1].to])
Min = min(Min, edge[i].cap - edge[i].flow);
for (int i = pre[t]; i != -1; i = pre[edge[i^1].to])
{
edge[i].flow += Min;
edge[i^1].flow -= Min;
cost += 1ll * edge[i].cost * Min;
}
flow += Min;
}
return flow;
}
int n, p, fd, fs, sd, ss;
int S, T;
LL a[maxn];
int main()
{
scanf("%d", &n);
LL sum = 0;
for (int i = 1; i <= n; i++) scanf("%lld", &a[i]), sum += a[i];
scanf("%d%d%d%d%d", &p, &fd, &fs, &sd, &ss);
S = 0, T = n*2 + 1;
init(T+1);
for (int i = 1; i <= n; i++)
{
build(S, i, inf, p);
build(S, i+n, a[i], 0);
build(i, T, a[i], 0);
if (i+fd <= n) build(i+n, i+fd, inf, fs);
if (i+sd <= n) build(i+n, i+sd, inf, ss);
}
for (int i = 2; i <= n; i++)
build(i-1, i, inf, 0),
build(i-1+n, i+n, inf, 0);
LL cost = 0;
MinCostMaxflow(S, T, cost);
printf("%lld\n", cost);
}
飞行员配对方案问题
裸二分图最大匹配,匈牙利算法。
#include <bits/stdc++.h>
#define FOPI freopen("in.txt", "r", stdin)
#define FOPO freopen("out.txt", "w", stdout)
using namespace std;
typedef long long LL;
const int maxn = 100 + 10;
vector<int> v[maxn];
int link[maxn], vis[maxn];
int ans[maxn];
int n, m, x, y;
void build(int x, int y)
{
v[x].push_back(y);
}
bool dfs(int k)
{
int sz = v[k].size();
for (int i = 0; i < sz; i++)
{
if (!vis[v[k][i]])
{
vis[v[k][i]] = 1;
if (link[v[k][i]] == -1 || dfs(link[v[k][i]]))
{
link[v[k][i]] = k;
ans[k] = v[k][i];
return true;
}
}
}
return false;
}
int hungary(int N)
{
int res = 0;
memset(ans, 0, sizeof(ans));
memset(link, -1, sizeof(link));
for (int i = 1; i <= N; i++)
{
memset(vis, 0, sizeof(vis));
if (dfs(i)) res++;
}
return res;
}
int main()
{
scanf("%d%d", &m, &n);
while(~scanf("%d %d", &x, &y) && x+y > 0) build(x, y);
int res = hungary(m);
if (!res) return printf("No Solution!\n"), 0;
printf("%d\n", hungary(m));
for (int i = 1; i <= m; i++)
if (ans[i]) printf("%d %d\n", i, ans[i]);
}
试题库问题
二分图多重匹配问题,一个点可以同时匹配多个点。
\(S\)向所有的试卷点连边,所有试题点向\(T\)连边。边权都是点的可以匹配的最大点数。
之后,试题点向试卷点连边,边权为\(1\)。再跑最大流即可。
找答案的时候找流量非零的边就可以了。
#include <bits/stdc++.h>
#define FOPI freopen("in.txt", "r", stdin)
#define FOPO freopen("out.txt", "w", stdout)
using namespace std;
typedef long long LL;
const int inf = 0x3f3f3f3f;
const int maxn = 1000 + 200;
const int maxm = 10 * maxn;
struct Edge
{
int to, next, cap, flow;
}edge[maxm];
int tot;
int head[maxn];
void init()
{
tot = 2;
memset(head, -1, sizeof(head));
}
void build(int u, int v, int w, int rw = 0)
{
edge[tot].to = v; edge[tot].cap = w; edge[tot].flow = 0;
edge[tot].next = head[u]; head[u] = tot++;
edge[tot].to = u; edge[tot].cap = 0; edge[tot].flow = 0;
edge[tot].next = head[v]; head[v] = tot++;
}
int Q[maxn];
int dep[maxn], cur[maxn], sta[maxn];
bool bfs(int s, int t, int n)
{
int front = 0, tail = 0;
memset(dep, -1, sizeof(dep[0]) * (n+1));
dep[s] = 0;
Q[tail++] = s;
while(front < tail)
{
int u = Q[front++];
for (int i = head[u]; i != -1; i = edge[i].next)
{
int v = edge[i].to;
if (edge[i].cap > edge[i].flow && dep[v] == -1)
{
dep[v] = dep[u] + 1;
if (v == t) return true;
Q[tail++] = v;
}
}
}
return false;
}
LL dinic(int s, int t, int n)
{
LL maxflow = 0;
while(bfs(s, t, n))
{
for (int i = 0; i < n; i++) cur[i] = head[i];
int u = s, tail = 0;
while(cur[s] != -1)
{
if (u == t)
{
int tp = inf;
for (int i = tail-1; i >= 0; i--)
tp = min(tp, edge[sta[i]].cap - edge[sta[i]].flow);
if (tp >= inf) return -1;
maxflow += tp;
for (int i = tail-1; i >= 0; i--)
{
edge[sta[i]].flow += tp;
edge[sta[i]^1].flow -= tp;
if (edge[sta[i]].cap - edge[sta[i]].flow == 0) tail = i;
}
u = edge[sta[tail]^1].to;
}
else if (cur[u] != -1 && edge[cur[u]].cap > edge[cur[u]].flow
&& dep[u]+1 == dep[edge[cur[u]].to])
{
sta[tail++] = cur[u];
u = edge[cur[u]].to;
}
else
{
while(u != s && cur[u] == -1) u = edge[sta[--tail]^1].to;
cur[u] = edge[cur[u]].next;
}
}
}
return maxflow;
}
int n, m, k, y, sum = 0;
int l[maxn];
int main()
{
init();
scanf("%d%d", &k, &n);
for (int i = 1; i <= k; i++) scanf("%d", &l[i+n]), sum += l[i+n];
for (int i = 1; i <= n; i++)
{
scanf("%d", &l[i]);
for (int j = 1; j <= l[i]; j++)
scanf("%d", &y), build(y+n, i, 1);
}
int S = 0, T = n+k+1;
for (int i = 1; i <= k; i++) build(S, i+n, l[i+n]);
for (int i = 1; i <= n; i++) build(i, T, 1);
if (dinic(S, T, T) == sum)
{
for (int i = 1; i <= k; i++)
{
printf("%d:", i);
for (int j = head[i+n]; j != -1; j = edge[j].next)
{
int v = edge[j].to;
if (edge[j].flow == edge[j].cap && v != S) printf(" %d", v);
}
puts("");
}
}
else printf("No Solution!\n");
}
圆桌问题
二分图多重匹配,和试题库问题很相似。
唯一的不同的就是判断非法的情况是匹配数不等于人总数。
#include <bits/stdc++.h>
#define FOPI freopen("in.txt", "r", stdin)
#define FOPO freopen("out.txt", "w", stdout)
using namespace std;
typedef long long LL;
const int inf = 0x3f3f3f3f;
const int maxn = 270 + 150 + 100;
const int maxm = 1e6 + 100;
struct Edge
{
int to, next, cap, flow;
}edge[maxm];
int tot;
int head[maxn];
void init()
{
tot = 2;
memset(head, -1, sizeof(head));
}
void build(int u, int v, int w, int rw = 0)
{
edge[tot].to = v; edge[tot].cap = w; edge[tot].flow = 0;
edge[tot].next = head[u]; head[u] = tot++;
edge[tot].to = u; edge[tot].cap = 0; edge[tot].flow = 0;
edge[tot].next = head[v]; head[v] = tot++;
}
int Q[maxn];
int dep[maxn], cur[maxn], sta[maxn];
bool bfs(int s, int t, int n)
{
int front = 0, tail = 0;
memset(dep, -1, sizeof(dep[0]) * (n+1));
dep[s] = 0;
Q[tail++] = s;
while(front < tail)
{
int u = Q[front++];
for (int i = head[u]; i != -1; i = edge[i].next)
{
int v = edge[i].to;
if (edge[i].cap > edge[i].flow && dep[v] == -1)
{
dep[v] = dep[u] + 1;
if (v == t) return true;
Q[tail++] = v;
}
}
}
return false;
}
LL dinic(int s, int t, int n)
{
LL maxflow = 0;
while(bfs(s, t, n))
{
for (int i = 0; i < n; i++) cur[i] = head[i];
int u = s, tail = 0;
while(cur[s] != -1)
{
if (u == t)
{
int tp = inf;
for (int i = tail-1; i >= 0; i--)
tp = min(tp, edge[sta[i]].cap - edge[sta[i]].flow);
if (tp >= inf) return -1;
maxflow += tp;
for (int i = tail-1; i >= 0; i--)
{
edge[sta[i]].flow += tp;
edge[sta[i]^1].flow -= tp;
if (edge[sta[i]].cap - edge[sta[i]].flow == 0) tail = i;
}
u = edge[sta[tail]^1].to;
}
else if (cur[u] != -1 && edge[cur[u]].cap > edge[cur[u]].flow
&& dep[u]+1 == dep[edge[cur[u]].to])
{
sta[tail++] = cur[u];
u = edge[cur[u]].to;
}
else
{
while(u != s && cur[u] == -1) u = edge[sta[--tail]^1].to;
cur[u] = edge[cur[u]].next;
}
}
}
return maxflow;
}
int n, m, k, y, sum = 0;
int l[maxn];
int main()
{
init();
scanf("%d%d", &n, &m);
int S = 0, T = n+m+1;
for (int i = 1; i <= n; i++)
scanf("%d", &l[i]), build(S, i, l[i]), sum += l[i];
for (int j = 1; j <= m; j++)
scanf("%d", &l[j+n]), build(j+n, T, l[j+n]);
for (int i = 1; i <= n; i++)
for (int j = 1; j <= m; j++) build(i, j+n, 1);
LL ans = dinic(S, T, T);
if (ans != sum) return printf("0\n"), 0; else printf("1\n");
for (int i = 1; i <= n; i++)
{
bool fst = true;
for (int j = head[i]; ~j; j = edge[j].next)
{
int y = edge[j].to;
if (edge[j].flow == 0 || y == S) continue;
if (!fst) printf(" ");
fst = false;
printf("%d", y-n);
}
puts("");
}
}
方格取数问题
将方格上的数划分成不能同时取的两部分。
\(S\)连向第一部分,第二部分连向\(T\),边权均为每个点的权值。
然后跑最大流,就能求出最小点权覆盖。
最大点权独立集 \(=\) 权值和 \(-\) 最小点权独立集
#include <bits/stdc++.h>
#define FOPI freopen("in.txt", "r", stdin)
#define FOPO freopen("out.txt", "w", stdout)
using namespace std;
typedef long long LL;
const int inf = 0x3f3f3f3f;
const int maxn = 100 * 100 + 200;
const int maxm = 4 * maxn;
struct Edge
{
int to, next, cap, flow;
}edge[maxm];
int tot;
int head[maxn];
void init()
{
tot = 2;
memset(head, -1, sizeof(head));
}
void build(int u, int v, int w, int rw = 0)
{
edge[tot].to = v; edge[tot].cap = w; edge[tot].flow = 0;
edge[tot].next = head[u]; head[u] = tot++;
edge[tot].to = u; edge[tot].cap = 0; edge[tot].flow = 0;
edge[tot].next = head[v]; head[v] = tot++;
}
int Q[maxn];
int dep[maxn], cur[maxn], sta[maxn];
bool bfs(int s, int t, int n)
{
int front = 0, tail = 0;
memset(dep, -1, sizeof(dep[0]) * (n+1));
dep[s] = 0;
Q[tail++] = s;
while(front < tail)
{
int u = Q[front++];
for (int i = head[u]; i != -1; i = edge[i].next)
{
int v = edge[i].to;
if (edge[i].cap > edge[i].flow && dep[v] == -1)
{
dep[v] = dep[u] + 1;
if (v == t) return true;
Q[tail++] = v;
}
}
}
return false;
}
LL dinic(int s, int t, int n)
{
LL maxflow = 0;
while(bfs(s, t, n))
{
for (int i = 0; i < n; i++) cur[i] = head[i];
int u = s, tail = 0;
while(cur[s] != -1)
{
if (u == t)
{
int tp = inf;
for (int i = tail-1; i >= 0; i--)
tp = min(tp, edge[sta[i]].cap - edge[sta[i]].flow);
if (tp >= inf) return -1;
maxflow += tp;
for (int i = tail-1; i >= 0; i--)
{
edge[sta[i]].flow += tp;
edge[sta[i]^1].flow -= tp;
if (edge[sta[i]].cap - edge[sta[i]].flow == 0) tail = i;
}
u = edge[sta[tail]^1].to;
}
else if (cur[u] != -1 && edge[cur[u]].cap > edge[cur[u]].flow
&& dep[u]+1 == dep[edge[cur[u]].to])
{
sta[tail++] = cur[u];
u = edge[cur[u]].to;
}
else
{
while(u != s && cur[u] == -1) u = edge[sta[--tail]^1].to;
cur[u] = edge[cur[u]].next;
}
}
}
return maxflow;
}
int n, m, x;
int id(int i, int j) { return (i-1)*m + j; }
int main()
{
init();
scanf("%d%d", &n, &m);
int S = 0, T = n*m+1, sum = 0;
for (int i = 1; i <= n; i++)
for (int j = 1; j <= m; j++)
{
scanf("%d", &x), sum += x;
if ((i+j) & 1)
{
build(S, id(i, j), x);
if (i > 1) build(id(i, j), id(i-1, j), inf);
if (j > 1) build(id(i, j), id(i, j-1), inf);
if (i != n) build(id(i, j), id(i+1, j), inf);
if (j != m) build(id(i, j), id(i, j+1), inf);
}
else build(id(i, j), T, x);
}
int ans = dinic(S, T, T);
printf("%d\n", sum - ans);
}
运输问题
裸的费用流。
\(S\)与货仓建\((x, 0)\)的边,货物与\(T\)建\((x, 0)\)的边,货仓与货物建\((inf, cost)\)的边,跑费用流。
#include <bits/stdc++.h>
#define FOPI freopen("in.txt", "r", stdin)
#define FOPO freopen("out.txt", "w", stdout)
using namespace std;
typedef long long LL;
const int inf = 0x3f3f3f3f;
const int maxn = 200 + 200;
const int maxm = maxn * maxn;
struct Edge
{
int to, next, cap, flow, cost;
}edge[maxm];
int tot, N;
int head[maxn], pre[maxn], dis[maxn];
bool vis[maxn];
void init(int n)
{
N = n;
tot = 0;
memset(head, -1, sizeof(head));
}
void build(int u, int v, int w, int cost)
{
edge[tot].to = v; edge[tot].cap = w;
edge[tot].cost = cost; edge[tot].flow = 0;
edge[tot].next = head[u]; head[u] = tot++;
edge[tot].to = u; edge[tot].cap = 0;
edge[tot].cost = -cost; edge[tot].flow = 0;
edge[tot].next = head[v]; head[v] = tot++;
}
bool relax(int x, int y, int i)
{
if (edge[i].cap > edge[i].flow && dis[y] > dis[x] + edge[i].cost)
{
dis[y] = dis[x] + edge[i].cost;
pre[y] = i;
return true;
}
return false;
}
bool SPFA(int s, int t)
{
queue<int> q;
for (int i = 0; i <= N; i++)
dis[i] = inf, vis[i] = false, pre[i] = -1;
dis[s] = 0, q.push(s), vis[s] = true;
while(!q.empty())
{
int x = q.front();
q.pop();
for (int i = head[x]; i != -1; i = edge[i].next)
{
int y = edge[i].to;
if (relax(x, y, i) && !vis[y]) vis[y] = true, q.push(y);
}
vis[x] = false;
}
return pre[t] != -1;
}
LL MinCostMaxflow(int s, int t, LL &cost)
{
LL flow = 0;
cost = 0;
while(SPFA(s, t))
{
int Min = inf;
for (int i = pre[t]; i != -1; i = pre[edge[i^1].to])
Min = min(Min, edge[i].cap - edge[i].flow);
for (int i = pre[t]; i != -1; i = pre[edge[i^1].to])
{
edge[i].flow += Min;
edge[i^1].flow -= Min;
cost += 1ll * edge[i].cost * Min;
}
flow += Min;
}
return flow;
}
int n, m;
int S, T;
int a[maxn], b[maxn], l[maxn][maxn];
void build(int res)
{
init(T);
int x;
for (int i = 1; i <= n; i++) build(S, i, a[i], 0);
for (int j = 1; j <= m; j++) build(j+n, T, b[j], 0);
for (int i = 1; i <= n; i++)
for (int j = 1; j <= m; j++) build(i, j+n, inf, l[i][j]*res);
}
int main()
{
scanf("%d%d", &n, &m);
for (int i = 1; i <= n; i++) scanf("%d", &a[i]);
for (int i = 1; i <= m; i++) scanf("%d", &b[i]);
for (int i = 1; i <= n; i++)
for (int j = 1; j <= m; j++) scanf("%d", &l[i][j]);
S = 0, T = n+m+1;
LL cost = 0;
build(1);
MinCostMaxflow(S, T, cost);
printf("%lld\n", cost);
build(-1);
MinCostMaxflow(S, T, cost);
printf("%lld\n", -cost);
}
分配问题
带权二分图的最佳匹配。
把容量都设为\(1\),把产生的效益看成花费,用费用流就好啦。(●'◡'●)
#include <bits/stdc++.h>
#define FOPI freopen("in.txt", "r", stdin)
#define FOPO freopen("out.txt", "w", stdout)
using namespace std;
typedef long long LL;
const int inf = 0x3f3f3f3f;
const int maxn = 200 + 200;
const int maxm = maxn * maxn;
struct Edge
{
int to, next, cap, flow, cost;
}edge[maxm];
int tot, N;
int head[maxn], pre[maxn], dis[maxn];
bool vis[maxn];
void init(int n)
{
N = n;
tot = 0;
memset(head, -1, sizeof(head));
}
void build(int u, int v, int w, int cost)
{
edge[tot].to = v; edge[tot].cap = w;
edge[tot].cost = cost; edge[tot].flow = 0;
edge[tot].next = head[u]; head[u] = tot++;
edge[tot].to = u; edge[tot].cap = 0;
edge[tot].cost = -cost; edge[tot].flow = 0;
edge[tot].next = head[v]; head[v] = tot++;
}
bool relax(int x, int y, int i)
{
if (edge[i].cap > edge[i].flow && dis[y] > dis[x] + edge[i].cost)
{
dis[y] = dis[x] + edge[i].cost;
pre[y] = i;
return true;
}
return false;
}
bool SPFA(int s, int t)
{
queue<int> q;
for (int i = 0; i <= N; i++)
dis[i] = inf, vis[i] = false, pre[i] = -1;
dis[s] = 0, q.push(s), vis[s] = true;
while(!q.empty())
{
int x = q.front();
q.pop();
for (int i = head[x]; i != -1; i = edge[i].next)
{
int y = edge[i].to;
if (relax(x, y, i) && !vis[y]) vis[y] = true, q.push(y);
}
vis[x] = false;
}
return pre[t] != -1;
}
LL MinCostMaxflow(int s, int t, LL &cost)
{
LL flow = 0;
cost = 0;
while(SPFA(s, t))
{
int Min = inf;
for (int i = pre[t]; i != -1; i = pre[edge[i^1].to])
Min = min(Min, edge[i].cap - edge[i].flow);
for (int i = pre[t]; i != -1; i = pre[edge[i^1].to])
{
edge[i].flow += Min;
edge[i^1].flow -= Min;
cost += 1ll * edge[i].cost * Min;
}
flow += Min;
}
return flow;
}
int n;
int S, T;
int c[maxn][maxn];
void Build_Graph(int res)
{
init(T);
for (int i = 1; i <= n; i++) build(S, i, 1, 0);
for (int j = 1; j <= n; j++) build(j+n, T, 1, 0);
for (int i = 1; i <= n; i++)
for (int j = 1; j <= n; j++) build(i, j+n, 1, c[i][j]*res);
}
int main()
{
scanf("%d", &n);
for (int i = 1; i <= n; i++)
for (int j = 1; j <= n; j++) scanf("%d", &c[i][j]);
S = 0, T = n+n+1;
LL cost = 0;
Build_Graph(1);
MinCostMaxflow(S, T, cost);
printf("%lld\n", cost);
Build_Graph(-1);
MinCostMaxflow(S, T, cost);
printf("%lld\n", -cost);
}