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
依我看,队名就叫「曾总说的都队」吧 🐵
—— · EOF · ——
真的什么也不剩啦 😖