230720 做题记录 // 费用流练习

雾竞跑了,树竞摆了,花竞蔫了,牲竞寄了,电竞笑了。


A. 订货

http://222.180.160.110:1024/contest/3820/problem/1

这个带继承关系的模型很熟悉,想到了 那一题。所以我们试着仿照这个方式来建图。

题目提到了单位费用,这简直就是直接把边的费用拍你脸上嘲讽。

我们拉一个大源点,朝每个月连一条容量为无穷大、费用为当月购买单位费用的边,表示每个月的购买。

拉一个大汇点,每个月朝它连一条容量为当月需求量、费用为 \(0\) 的边,表示每个月的需求。

再让每个月朝下一个月连一条容量为仓库容量、费用为贮存费用的边,表示继承。跑一个最小费用最大流即可。

#define int long long
namespace XSC062 {
using namespace fastIO;
const int maxn = 405;
const int inf = 1e18;
const int maxm = 5e5 + 5;
struct _ {
	int v, c, w, n;
	_() {}
	_(int v1, int c1, int w1, int n1) {
		v = v1, c = c1, w = w1, n = n1;
	}
};
_ u[maxm];
bool inq[maxn];
int n, m, S, x, res;
int gs, gt, tot = 1;
int h[maxn], dis[maxn];
int fl[maxn], pre[maxn];
inline int min(int x, int y) {
	return x < y ? x : y;
}
inline bool SPFA(int s, int n) {
	std::queue<int> q;
	std::fill(dis + 1, dis + n + 1, inf);
	q.push(s), dis[s] = 0, inq[s] = 1;
	pre[s] = inf, pre[gt] = 0, fl[s] = inf;
	while (!q.empty()) {
		int f = q.front();
		q.pop(), inq[f] = 0;
		for (int i = h[f]; i; i = u[i].n) {
			if (u[i].c == 0)
				continue;
			int v = u[i].v, w = u[i].w;
			if (dis[v] > dis[f] + w) {
				pre[v] = i ^ 1;
				dis[v] = dis[f] + w;
				fl[v] = min(fl[f], u[i].c);
				if (!inq[v])
					inq[v] = 1, q.push(v);
			}
		}
	}
	return pre[gt];
}
inline void SSP(int s, int n) {
	int p, mn, d;
	while (SPFA(s, n)) {
		mn = fl[gt], d = 0;
		for (p = gt; p != s; p = u[pre[p]].v) {
			u[pre[p]].c += mn;
			u[pre[p] ^ 1].c -= mn;
			d += u[pre[p] ^ 1].w;
		}
		res += mn * d;
	}
	return;
}
inline void add(int x, int y, int c, int w) {
	u[++tot] = _(y, c, w, h[x]);
	h[x] = tot;
	return;
}
inline void addf(int x, int y, int c, int w) {
	add(x, y, c, w), add(y, x, 0, -w);
	return;
}
int main() {
	read(n), read(m), read(S);
	gs = n + 1, gt = gs + 1;
	for (int i = 1; i <= n; ++i) {
		read(x);
		addf(i, gt, x, 0);
		if (i != n)
			addf(i, i + 1, S, m);
	}
	for (int i = 1; i <= n; ++i) {
		read(x);
		addf(gs, i, inf, x);
	}	
	SSP(gs, gt);
	print(res, '\n');
	return 0;
}
} // namespace XSC062
#undef int  

B. 网络扩容

http://222.180.160.110:1024/contest/3820/problem/2

鉴于一道费用流不会无缘无故先让你求一遍最大流,我们先持观望态度,暂且认为最大流对题目有提示作用 而不是说这道题就是个缝合怪

其实看完题我们就悟了,这怎么这么像上下界网络流那个差量网络呀,要不我们试试这么干?

我们先求得普通网络中的最大流,然后每条边减去流量,就成为了一个「差量网络 Pro」。那么我们现在就要通过扩容让该网络中的最大流变为 \(K\)。对于扩容的操作,不难想到把每条边的边权设为正无穷,然后费用设为扩容费用。

现在有了一个问题:原图中未留满的边,在现在的新网络中的残余容量应该如何处理呢?很简单,我们就把它当作已经扩过了这么多容,通过拆边操作拆出来一条容量为原图中残余容量、费用为 \(0\)「会员通道」,那么算法就会优先选择这条边。

怎么去控制流量为 \(K\)?联想到之前的拆边操作,我们考虑拆点。在 \(1\)\(N\) 中任选一个拆开作为新的源点 / 汇点,新点和旧点之间的容量为 \(K\)、费用为 \(0\) 即可。

然后跑一个最小费用最大流就行。该说不说题目的正解思路引导做得还挺好的

其实注意到在跑完最大流之后,所有正向边的残余容量已经求得,只要在跑最大流时令所有边的费用为 \(0\)(毕竟最大流不关心费用),就可以沿用原图,只加新边再跑费用流。

#define int long long
namespace XSC062 {
using namespace fastIO;
const int inf = 1e18;
const int maxn = 1e3 + 5;
const int maxm = 5e5 + 5;
struct _ {
	int v, c, w, n;
	_() {}
	_(int v1, int c1, int w1, int n1) {
		v = v1, c = c1, w = w1, n = n1;
	}
};
struct __ { int x, y, c, w; };
_ u[maxm];
__ w[maxm];
bool inq[maxn];
int n, m, k, res;
int gs, gt, tot = 1;
int h[maxn], dis[maxn];
int fl[maxn], pre[maxn];
int vis[maxn], now[maxn], dep[maxn];
inline int min(int x, int y) {
	return x < y ? x : y;
}
bool BFS(int n) {
	std::fill(vis + 1, vis + n + 1, 0);
	std::fill(dep + 1, dep + n + 1, 0); 
	std::queue<int> q;
	dep[gs] = 1, vis[gs] = 1;
	q.push(gs), now[gs] = h[gs];
	while (!q.empty()) {
		int f = q.front();
		q.pop();
		for (int i = h[f]; i; i = u[i].n) {
			int v = u[i].v, w = u[i].c;
			if (vis[v] == 1 || w == 0)
				continue;
			vis[v] = 1, now[v] = h[v];
			dep[v] = dep[f] + 1, q.push(v);
			if (v == gt)
				return 1;
		}
	}
	return 0;
}
int findP(int x, int flow = inf) {
	if (x == gt)
		return flow;
	int rest = flow, i;
	for (i = now[x]; rest && i; i = u[i].n) {
		int v = u[i].v, w = u[i].c;
		now[x] = i;
		if (dep[v] != dep[x] + 1 || w == 0)
			continue;
		int t = findP(v, min(rest, w));
		if (t == 0)
			dep[v] = 0;
		rest -= t;
		u[i].c -= t, u[i ^ 1].c += t;
	}
	return flow - rest;
}
inline int Dinic(int n) {
	int res = 0;
	while (BFS(n)) {
		int t = findP(gs);
		while (t) {
			res += t;
			t = findP(gs);
		}
	}
	return res;
}
inline bool SPFA(int s, int n) {
	std::queue<int> q;
	std::fill(dis + 1, dis + n + 1, inf);
	q.push(s), dis[s] = 0, inq[s] = 1;
	pre[s] = inf, pre[gt] = 0, fl[s] = inf;
	while (!q.empty()) {
		int f = q.front();
		q.pop(), inq[f] = 0;
		for (int i = h[f]; i; i = u[i].n) {
			if (u[i].c == 0)
				continue;
			int v = u[i].v, w = u[i].w;
			if (dis[v] > dis[f] + w) {
				pre[v] = i ^ 1;
				dis[v] = dis[f] + w;
				fl[v] = min(fl[f], u[i].c);
				if (!inq[v])
					inq[v] = 1, q.push(v);
			}
		}
	}
	return pre[gt];
}
inline void SSP(int s, int n) {
	int p, mn, d;
	while (SPFA(s, n)) {
		mn = fl[gt], d = 0;
		for (p = gt; p != s; p = u[pre[p]].v) {
			u[pre[p]].c += mn;
			u[pre[p] ^ 1].c -= mn;
			d += u[pre[p] ^ 1].w;
		}
		res += mn * d;
	}
	return;
}
inline void add(int x, int y, int c, int w) {
	u[++tot] = _(y, c, w, h[x]);
	h[x] = tot;
	return;
}
inline void addf(int x, int y, int c, int w = 0) {
	add(x, y, c, w), add(y, x, 0, -w);
	return;
}
int main() {
	read(n), read(m), read(k);
	gs = 1, gt = n;
	for (int i = 1; i <= m; ++i) {
		read(w[i].x), read(w[i].y);
		read(w[i].c), read(w[i].w);
		addf(w[i].x, w[i].y, w[i].c);
	}
	print(Dinic(n), ' ');
	gs = n + 1, addf(gs, 1, k, 0);
	for (int i = 1; i <= m; ++i)
		addf(w[i].x, w[i].y, inf, w[i].w);
	SSP(gs, gt);
	print(res, '\n');
	return 0;
}
} // namespace XSC062
#undef int

C. 航班安排

222.180.160.110:1024/contest/3820/problem/3

很简单一个道理,时间的具体值对我们来说并不重要。能到就是能到,不能到就是不能到。

边权矩阵也并不是全部有用,这条边和任务有关系吗?没有,那就当它是空气。

那什么会对我们产生限制?飞机数量。故容量由飞机数量决定。什么是我们要最大化的值?收益,故我们的费用是支出。

如果飞机能在一个任务结束之后,在另一个任务开始之前飞过去,那就将两个任务连边,容量为 \(1\),费用为两点间花费。特别地,应将机场拆为大源点和大汇点,并在二者中任选其一拆点(和 T2 类似),好作出 \(K\) 的流量限制。

还有一个小细节,对于一个任务的流量我们也要加以约束,不然碰到流入 \(2\) 流出 \(2\) 这种平衡但不合法的情况就不行了,所以对于任务我们也要按老套路拆点。

#define int long long
namespace XSC062 {
using namespace fastIO;
const int inf = 1e18;
const int maxn = 405;
const int maxm = 4e5 + 5;
struct _ {
	int v, c, w, n;
	_() {}
	_(int v1, int c1, int w1, int n1) {
		v = v1, c = c1, w = w1, n = n1;
	}
};
struct __ {
	int x, y, l, r, c;
	bool operator< (const __ &q) const {
		return l < q.l;
	}
};
_ u[maxm];
__ w[maxn];
bool inq[maxn];
int gs, gt, tot = 1;
int h[maxn], dis[maxn];
int fl[maxn], pre[maxn];
int n, m, k, T, res, fs, ft;
int t[maxn][maxn], f[maxn][maxn];
inline int min(int x, int y) {
	return x < y ? x : y;
}
inline bool SPFA(int s, int n) {
	std::queue<int> q;
	std::fill(dis + 1, dis + n + 1, inf);
	q.push(s), dis[s] = 0, inq[s] = 1;
	pre[s] = inf, pre[gt] = 0, fl[s] = inf;
	while (!q.empty()) {
		int f = q.front();
		q.pop(), inq[f] = 0;
		for (int i = h[f]; i; i = u[i].n) {
			if (u[i].c == 0)
				continue;
			int v = u[i].v, w = u[i].w;
			if (dis[v] > dis[f] + w) {
				pre[v] = i ^ 1;
				dis[v] = dis[f] + w;
				fl[v] = min(fl[f], u[i].c);
				if (!inq[v])
					inq[v] = 1, q.push(v);
			}
		}
	}
	return pre[gt];
}
inline void SSP(int s, int n) {
	int p, mn, d;
	while (SPFA(s, n)) {
		mn = fl[gt], d = 0;
		for (p = gt; p != s; p = u[pre[p]].v) {
			u[pre[p]].c += mn;
			u[pre[p] ^ 1].c -= mn;
			d += u[pre[p] ^ 1].w;
		}
		res += mn * d;
	}
	return;
}
inline void add(int x, int y, int c, int w) {
	u[++tot] = _(y, c, w, h[x]);
	h[x] = tot;
	return;
}
inline void addf(int x, int y, int c, int w) {
	add(x, y, c, w), add(y, x, 0, -w);
	return;
}
int main() {
	read(n), read(m), read(k), read(T);
	fs = 2 * m + 1, ft = 2 * m + 2;
	gs = 2 * m + 3, gt = 2 * m + 4;
	addf(gs, fs, k, 0), addf(ft, gt, k, 0);
	for (int i = 1; i <= n; ++i) {
		for (int j = 1; j <= n; ++j)
			read(t[i][j]);
	}
	for (int i = 1; i <= n; ++i) {
		for (int j = 1; j <= n; ++j)
			read(f[i][j]);
	}
	for (int i = 1; i <= m; ++i) {
		read(w[i].x), read(w[i].y);
		read(w[i].l), read(w[i].r);
		read(w[i].c), ++w[i].x, ++w[i].y;
	}
	for (int i = 1; i <= m; ++i) {
		addf(i + m, i, 1, 0);
		if (t[1][w[i].x] <= w[i].l) {
			addf(fs, i + m, 1,
				f[1][w[i].x] - w[i].c);
		}
		if (w[i].r + t[w[i].y][1] <= T)
			addf(i, ft, 1, f[w[i].y][1]);
		for (int j = 1; j <= m; ++j) {
			if (i != j && w[i].r +
				t[w[i].y][w[j].x] <= w[j].l) {
				addf(i, j + m, 1,
					f[w[i].y][w[j].x] - w[j].c);
			}
		}
	}
	SSP(gs, gt);
	print(-res, '\n');
	return 0;
}
} // namespace XSC062
#undef int

D. 修车

http://222.180.160.110:1024/contest/3820/problem/4

顾客数一定,所谓平均等待时间不过是个幌子,只需要求得最小等待总时间。

技术人员不能同时修两辆车,\(M\) 的范围又这么小,不往拆点想都难。可问题来了,怎么拆呢?

我一开始的想法是,用点 \((i, j)\) 表示人 \(i\) 修车 \(j\),但是这样建图怎样也无法达到目的。

于是我添加了一个维度 \(k\),用点 \((i, j, k)\) 表示人 \(i\) 修车 \(j\),并且这是他修的倒数第 \(k\) 辆车,这样建图就轻而易举了。但接下来就面临了一个问题:这数据范围跑不过。于是乎审视我们的点,其实 \(j\) 这个维度是可以被合并的,只保留 \((i, k)\),因为不可能存在两辆车同时为人 \(i\) 的倒数第 \(k\) 辆车。

故将大源点和每辆车连边,容量为 \(1\),费用为 \(0\);将每辆车 \(j\) 和每个 \((i, k)\) 连边,容量为 \(1\),费用为 \(T_{i, j}\times k\)

这里有一点点费用提前计算的意思,所以就直接将每个 \((i, j)\) 和大汇点连边,容量为 \(1\),费用为 \(0\)

#define int long long
namespace XSC062 {
using namespace fastIO;
const int inf = 1e18;
const int maxn = 1e4 + 5;
const int maxm = 1e6 + 5;
struct _ {
	int v, c, w, n;
	_() {}
	_(int v1, int c1, int w1, int n1) {
		v = v1, c = c1, w = w1, n = n1;
	}
};
_ u[maxm];
bool inq[maxn];
int n, m, x, res;
int gs, gt, tot = 1;
int h[maxn], dis[maxn];
int fl[maxn], pre[maxn];
inline int fun(int i, int j) {
	return n + (i - 1) * n + j;
}
inline int min(int x, int y) {
	return x < y ? x : y;
}
inline bool SPFA(int s, int n) {
	std::queue<int> q;
	std::fill(dis + 1, dis + n + 1, inf);
	q.push(s), dis[s] = 0, inq[s] = 1;
	pre[s] = inf, pre[gt] = 0, fl[s] = inf;
	while (!q.empty()) {
		int f = q.front();
		q.pop(), inq[f] = 0;
		for (int i = h[f]; i; i = u[i].n) {
			if (u[i].c == 0)
				continue;
			int v = u[i].v, w = u[i].w;
			if (dis[v] > dis[f] + w) {
				pre[v] = i ^ 1;
				dis[v] = dis[f] + w;
				fl[v] = min(fl[f], u[i].c);
				if (!inq[v])
					inq[v] = 1, q.push(v);
			}
		}
	}
	return pre[gt];
}
inline void SSP(int s, int n) {
	int p, mn, d;
	while (SPFA(s, n)) {
		mn = fl[gt], d = 0;
		for (p = gt; p != s; p = u[pre[p]].v) {
			u[pre[p]].c += mn;
			u[pre[p] ^ 1].c -= mn;
			d += u[pre[p] ^ 1].w;
		}
		res += mn * d;
	}
	return;
}
inline void add(int x, int y, int c, int w) {
	u[++tot] = _(y, c, w, h[x]);
	h[x] = tot;
	return;
}
inline void addf(int x, int y, int c, int w) {
	add(x, y, c, w), add(y, x, 0, -w);
	return;
}
int main() {
	read(m), read(n);
	gs = n * m + n + 1, gt = gs + 1;
	for (int i = 1; i <= n; ++i)
		addf(gs, i, 1, 0);
	for (int i = 1; i <= n * m; ++i)
		addf(i + n, gt, 1, 0);
	for (int i = 1; i <= n; ++i) {
		for (int j = 1; j <= m; ++j) {
			read(x);
			for (int k = 1; k <= n; ++k) {
				addf(i, fun(j, k),
							1, x * k);
			}
		}
	}
	SSP(gs, gt);
	printf("%.2lf", res * 1.0 / n);
	return 0;
}
} // namespace XSC062
#undef int

E. 连连看

http://222.180.160.110:1024/contest/3820/problem/5

样例已经告诉了我们事实:小心删数,因为会有数同时出现在多组勾股数中。

还是熟悉的单点 \(1\) 流量限制,故拆点为入点和出点,入点连大源点容量为 \(1\) 费用为 \(0\),出点连大汇点容量为 \(1\) 费用为 \(0\),满足条件的 \(x\)\(y\) 我们为了不整细节就暴力地连双向边然后跑最大费用最大流。

由于双向边这个神必操作,最后的最大流和最大费用都会翻倍,输出的时候要减半。

#define int long long
namespace XSC062 {
using namespace fastIO;
const int inf = 1e18;
const int maxn = 2e3 + 5;
const int maxm = 4e5 + 5;
struct _ {
	int v, c, w, n;
	_() {}
	_(int v1, int c1, int w1, int n1) {
		v = v1, c = c1, w = w1, n = n1;
	}
};
_ u[maxm];
bool inq[maxn];
int l, r, res, cnt;
bool f[maxn][maxn];
int gs, gt, tot = 1;
int h[maxn], dis[maxn];
int fl[maxn], pre[maxn];
int gcd(int x, int y) {
	return y ? gcd(y, x % y) : x;
}
inline int min(int x, int y) {
	return x < y ? x : y;
}
inline bool SPFA(int s, int n) {
	std::queue<int> q;
	std::fill(dis + 1, dis + n + 1, -inf);
	q.push(s), dis[s] = 0, inq[s] = 1;
	pre[s] = inf, pre[gt] = 0, fl[s] = inf;
	while (!q.empty()) {
		int f = q.front();
		q.pop(), inq[f] = 0;
		for (int i = h[f]; i; i = u[i].n) {
			if (u[i].c == 0)
				continue;
			int v = u[i].v, w = u[i].w;
			if (dis[v] < dis[f] + w) {
				pre[v] = i ^ 1;
				dis[v] = dis[f] + w;
				fl[v] = min(fl[f], u[i].c);
				if (!inq[v])
					inq[v] = 1, q.push(v);
			}
		}
	}
	return pre[gt];
}
inline void SSP(int s, int n) {
	int p, mn, d;
	while (SPFA(s, n)) {
		mn = fl[gt], d = 0;
		for (p = gt; p != s; p = u[pre[p]].v) {
			u[pre[p]].c += mn;
			u[pre[p] ^ 1].c -= mn;
			d += u[pre[p] ^ 1].w;
		}
		res += mn * d, cnt += mn;
	}
	return;
}
inline void add(int x, int y, int c, int w) {
	u[++tot] = _(y, c, w, h[x]);
	h[x] = tot;
	return;
}
inline void addf(int x, int y, int c, int w) {
	if (f[x][y])
		return;
	f[x][y] = f[y][x] = 1;
	add(x, y, c, w), add(y, x, 0, -w);
	return;
}
inline bool check(int x, int y) {
	int z = sqrt(x * x - y * y);
	if (z * z + y * y == x * x)
		return (gcd(z, y) == 1);
	return 0;
}
int main() {
	read(l), read(r);
	gs = 2 * r + 1, gt = 2 * r + 2;
	for (int i = l; i <= r; ++i) {
		addf(gs, i, 1, 0);
		addf(i + r, gt, 1, 0);
		for (int j = l; j < i; ++j) {
			if (check(i, j)) {
				addf(j, i + r, 1, i + j);
				addf(i, j + r, 1, i + j);
			}
		}
	}
	SSP(gs, gt);
	print(cnt / 2, ' '), print(res / 2, '\n');
	return 0;
}
} // namespace XSC062
#undef int

F. dance 跳舞

http://222.180.160.110:1024/contest/3820/problem/6


依我看,队名就叫「曾总说的都队」吧 🐵

posted @ 2023-07-20 14:53  XSC062  阅读(37)  评论(0编辑  收藏  举报