最大流

\(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\)即可。

剪刀石头布

传送门

UVA1104 芯片难题 Chips Challenge

传送门