网络流
最大流
\(Dinic\)
直接上代码CwQwC。
#include <iostream>
#include <cstdio>
#include <cstring>
#include <queue>
using namespace std;
const int INF = 999999999;
const int N = 10050;
const int M = 200050;
int n, m, s, t, head[N], num = 1, dis[N];
struct Node
{
int next, to, dis;
} edge[M * 2];
void Addedge(int u, int v, int w)
{
edge[++num]= (Node){head[u], v, w};
head[u] = num;
}
template <class T>
void Read(T &x)
{
x = 0; int p = 0; char st = getchar();
while (st < '0' || st > '9') p = (st == '-'), st = getchar();
while (st >= '0' && st <= '9') x = (x << 1) + (x << 3) + st - '0', st = getchar();
x = p ? -x : x;
return;
}
template <class T>
void Put(T x)
{
if (x < 0) putchar('-'), x = -x;
if (x > 9) Put(x / 10);
putchar(x % 10 + '0');
return;
}
bool Bfs()
{
queue<int> q;
for (int i = 1; i <= n; i++) dis[i] = 0;
dis[s] = 1; q.push(s);
while (!q.empty())
{
int u = q.front(); q.pop();
for (int i = head[u]; i; i = edge[i].next)
if (!dis[edge[i].to] && edge[i].dis)
{
dis[edge[i].to] = dis[u] + 1;
q.push(edge[i].to);
if (edge[i].to == t) return 1;
}
}
return 0;
}
int Dinic(int x, int flow)
{
if (x == t || !flow) return flow;
int rest = flow;
for (int i = head[x]; i && rest; i = edge[i].next)
if (edge[i].dis && dis[edge[i].to] == dis[x] + 1)
{
int v = edge[i].to;
int tmp = Dinic(v, min(rest, edge[i].dis));
rest -= tmp;
edge[i].dis -= tmp;
edge[i ^ 1].dis += tmp;
if (!tmp) dis[v] = 0;
}
return flow - rest;
}
int main()
{
Read(n); Read(m); Read(s); Read(t);
int u, v, w;
for (int i = 1; i <= m; i++)
{
Read(u); Read(v); Read(w);
Addedge(u, v, w); Addedge(v, u, 0);
}
int maxflow = 0, tmp;
while (Bfs())
{
tmp = Dinic(s, INF);
if (tmp) maxflow += tmp;
}
Put(maxflow);
return 0;
}
士兵占领
\(Dining\)
每个\(S\)向饮料连一条容量为\(1\)的有向边。
每个食物向\(T\)连一条容量为\(1\)的有向边。
这样是为了限制每个饮料和食物都只能用一次。
把每个奶牛拆成两个点,之间连上一条容量为\(1\)的有向边。
限制奶牛只会被满足一次要求。
然后每个奶牛喜欢的饮料向奶牛连容量为\(1\)的有向边,每个奶牛向它喜欢的食物连容量为\(1\)的有向边。
跑最大流即可。
最大获利
最小路径覆盖问题
首先每个点只能经过一次,所以把每个点\(i\)拆成两个点\(i_a\)和\(i_b\)之间连一条容量为\(1\)的有向边来限制。
\(S\)向每个\(i_a\)连一条容量为\(1\)的有向边。
每个\(i_b\)向\(T\)连容量为\(1\)的有向边。
这样连限制了每个点只能在一条路径上。
如果原图中有一条边\((u,v)\),那么就从\(u_b\)向\(v_a\)连容量为\(1\)的有向边。
原图如果每个点都分配一条路径就是点数个路径。
如果照这样建图跑出来的最大流就是最多可以合并多少路径,每多合并一条路径,总的路径数量就减一。
那么需要的路径数就是点数减去最大流。
最长不下降子序列问题
首先动态规划求出\(f_i\),表示以第\(i\)位为开头的最长上升序列的长度,求出最长上升序列长度\(k\)。
把序列每位\(i\)拆成两个点\(i_a\)和\(i_b\),从\(i_a\)到\(i_b\)连接一条容量为\(1\)的有向边。
如果\(f_i = k\),从\(S\)到\(i_a\)连接一条容量为\(1\)的有向边。
如果\(f_i = 1\),从\(i_b\)到\(T\)连接一条容量为\(1\)的有向边。
如果\(j>i\)且\(a_i < a_j\)且\(f_j + 1 = f _i\),从\(i_b\)到\(j_a\)连接一条容量为\(1\)的有向边。
求出最大流就是第二问的结果,第三问只要把\((1_a, 1_b)\),\((n_a, n_b)\),\((S, 1_a)\),\((n_b, T)\)的容量改为\(INF\)即可。
方格取数问题
如果找出哪些点不取就变成最小割问题就可以跑最大流了。
如果取了一个点那么它四周的点都不能取,于是我们想到可以在中间连容量为\(INF\)的有向边边来限制。
继续观察发现不能同时取的两个点的横纵坐标之和的奇偶性不同。
那么我们按横纵坐标奇偶性分成两类,建立二分图。
\(S\)向每个横纵坐标之和为偶数位置连容量为权值的有向边,每个横纵坐标之和为奇数位置向\(T\)连容量为权值的有向边。
然后不能同时取的点连上就行了。
答案就是所有权值之和减去最大流。
魔术球问题
首先每个数拆成两个点\(i_a\)和\(i_b\),如果\(j\)能放在\(i\)的上面就从\(i_b\)向\(j_a\)连容量为\(1\)的有向边。
然后和最小路径覆盖问题一样建图。
发现最小路径覆盖数就是最少所需要的柱子数量。
一个一个数加进残量网络直到最少所需柱子数量大于\(n\),答案就是此时加进去的数的个数减一。
圆桌问题
\(S\)向每个单位连容量为\(r_i\)的有向边;每个桌子向\(T\)连容量为\(c_i\)的有向边。
每个单位向每个桌子连容量为\(1\)的有向边。
最大流如果是所有人的总数则有解。
\(happiness\)
文理分科
家园 / 星际转移问题
费用流
\(Dinic + Spfa\)
#include <iostream>
#include <cstdio>
#include <cstring>
using namespace std;
const int INF = 1e9;
const int N = 100050;
int n, m, s, t, head[N], q[N], cur[N], vis[N], l, r, ans, maxflow, num = 1, dis[N], nxt[N];
struct Node
{
int to, flow, dis;
} edge[N * 2];
template <class T>
void Read(T &x)
{
x = 0; int p = 0; char st = getchar();
while (st < '0' || st > '9') p = (st == '-'), st = getchar();
while (st >= '0' && st <= '9') x = (x << 1) + (x << 3) +st - '0', st = getchar();
x = p ? -x : x;
return;
}
template <class T>
void Put(T x)
{
if (x < 0) putchar('-'), x = -x;
if (x > 9) Put(x / 10);
putchar(x % 10 + '0');
return;
}
void Addedge(int u, int v, int w, int c)
{
edge[++num] = (Node){v, w, c};
nxt[num] = head[u];
head[u] = num;
return;
}
int Bfs()
{
for (int i = 1; i <= n; i++) vis[i] = 0, dis[i] = INF;
dis[s] = 0; l = 0; r = 1; q[1] = s;
while (l < r)
{
int u = q[++l]; vis[u] = 0;
for(int i = head[u]; i != -1; i = nxt[i])
{
int v = edge[i].to;
if (dis[u] + edge[i].dis < dis[v] && edge[i].flow > 0)
{
dis[v] = dis[u] + edge[i].dis;
if (!vis[v])
{
if (dis[v] < dis[q[l + 1]]) q[l--] = v;
else q[++r] = v;
vis[v] = 1;
}
}
}
}
return dis[t] != INF;
}
int Dinic(int x, int flow)
{
if (x == t || !flow) return flow;
int add = 0, f;
vis[x] = 1;
for (int i = cur[x]; i != -1 && flow > 0; i = nxt[i])
{
cur[x] = i;
int v = edge[i].to;
if (!vis[v] && dis[v] == dis[x] + edge[i].dis && edge[i].flow > 0 && (f = Dinic(v, min(edge[i].flow, flow))))
{
add += f;
ans += f * edge[i].dis;
edge[i].flow -= f;
edge[i ^ 1].flow += f;
flow -= f;
}
}
vis[x] = 0;
return add;
}
void Solve()
{
while (Bfs())
{
for (int i = 1; i <= n; i++) cur[i] = head[i];
maxflow += Dinic(s, INF);
}
return;
}
int main()
{
Read(n); Read(m); Read(s); Read(t);
for (int i = 1; i <= n; i++) head[i] = -1, dis[i] = INF;
for (int i = 1; i <= m; i++)
{
int u, v, w, c;
Read(u); Read(v); Read(w); Read(c);
Addedge(u, v, w, c); Addedge(v, u, 0, -c);
}
Solve();
Put(maxflow); putchar(' '); Put(ans);
return 0;
}
餐巾计划问题
将每一天拆成两个点分别表示早上和晚上。
\(S\)向每天晚上连一条容量为当天使用餐巾数量,费用为零的有向边,表示获得脏餐巾。
每天早上向\(T\)连一条容量为当天要用的餐巾数量,费用为零的有向边,表示足量提供所需的餐巾数量。
每天晚上向第二天晚上连容量为\(INF\),费用为零的边有向边,表示第一天的餐巾可以留到第二天使用。
每天晚上向加上快洗时间的那天的早上连容量为\(INF\),费用为快洗费用的有向边,表示快洗。
每天晚上向加上慢洗时间的那天的早上连容量为\(INF\),费用为慢洗费用的有向边,表示慢洗。
\(S\)向每天早上连容量为\(INF\),费用为买餐巾的费用的有向边,表示购买餐巾。
最长k可重区间集问题
两种建模方式
第一种按左端点排序所有区间,把每个区间拆分看做两个顶点\(i_a\)和\(i_b\)。
建立虚点\(S'\)。
连接\(S\)到\(S’\)一条容量为\(k\),费用为\(0\)的有向边。
从\(S'\)到每个\(i_a\)连接一条容量为\(1\),费用为\(0\)的有向边。
从每个\(i_b\)到\(T\)连接一条容量为\(1\),费用为\(0\)的有向边。
从每个顶点\(i_a\)到\(i_b\)连接一条容量为\(1\),费用为区间长度的有向边。
对于每个区间\(i\),与它右边的不相交的所有区间\(j\)各连一条容量为\(1\),费用为\(0\)的有向边。
第二种离散化所有区间的端点,把每个端点看做一个顶点。
同样建立虚点\(S'\)。
从\(S\)到顶点\(1\)(最左边顶点)连接一条容量为\(k\),费用为\(0\)的有向边。
从顶点\(2n\)(最右边顶点)到\(T\)连接一条容量为\(k\),费用为\(0\)的有向边。
从顶点\(i\)到顶点\(i+1\),连接一条容量为无穷大,费用为\(0\)的有向边。
对于每个区间\([a, b]\),从\(a\)对应的顶点\(i\)到\(b\)对应的顶点\(j\)连接一条容量为\(1\),费用为区间长度的有向边。
数字梯形问题
点和边都不能相交的情况只需将每个点都拆成两个点\(i_a\)和\(i_b\),中间连一条容量为\(1\),费用为点权的的有向边限制每个点只能经过一次,然后原图如果有一条边\((i,j)\),则把\(i_b\)向\(j_a\)连一条容量为\(1\),费用为零的有向边。
点可以相交只需把点对应的边的容量改成\(INF\)即可。
边可以相交只需把边对应的边的容量改成\(INF\)即可。