题目链接
题意
宇宙中有n个星球,你的任务是用最短的时间把k个超级计算机从星球S运送到星球T。每个超级计算机需要一整艘飞船来运输。行星之间有m 条双向隧道,每条隧道需要一天时间来通过,且不能有两艘飞船同时使用同一条隧道。隧道不会连接两个相同的行星,且每一对行星之间最多只有一条隧道。
输入格式
输入包含多组数据。每组数据第一行包含5个正整数n, m, k, S, T(2≤n≤50,1≤m≤200,1≤k≤50,1≤S,T≤N,S≠T)。以下m行每行包含两个不同的整数u和v,表示行星u和v之间有一条隧道。注意,隧道是双向的,但每一天只有一艘飞船能穿过一条隧道。另外,两艘飞船不能同时沿着相反的方向穿过同一个隧道。
输出格式
输出第一行为最少需要的天数L。以下L行每行描述一条的行动。每行第一个整数为Ci,即当天移动的飞船数。接下来有Ci对整数(A, B),表示有一艘飞船从行星A出发到达行星B。输入保证有解。
分析
题面说每一天只有一艘飞船能穿过一条隧道且两艘飞船不能同时沿着相反的方向穿过同一个隧道,可以和最优路径覆盖(不同路径不能有重边且路径长度尽量短)以及最小割联系起来。
假设图上有桥,那么只有一条最短路对运输结果起决定作用,若最短路长度为
d
0
d_0
d0,则最少需要的天数为
k
+
d
0
−
1
k+d_0-1
k+d0−1;
如果没有桥,且最短路有
c
0
c_0
c0条(
c
0
>
1
c_0>1
c0>1),只用这些最短路时需要的最少天数为
⌈
k
c
0
⌉
+
d
0
−
1
\left \lceil \frac{k}{c_0} \right \rceil +d_0-1
⌈c0k⌉+d0−1,假设还有一条长度为
d
1
d_1
d1的次短路,显然
d
1
<
⌈
k
c
0
⌉
+
d
0
−
1
d_1<\left \lceil \frac{k}{c_0} \right \rceil +d_0-1
d1<⌈c0k⌉+d0−1时把次短路也用于运输可以降低总时间。
因此,求解思路出来了:找出图中不同路径不能有重边且路径长度尽量短的所有st路径,将这些路径按长度排序,按长度分层直接可找出最少需要的天数,然后按照长度越短的路径越优先使用的原则构造运输方案即可。
怎么求图中不同路径不能有重边且路径长度尽量短的所有st路径呢?跑一次最小费用最大流后,反复从源点s出发沿着有流的正向边走到汇点t就能找出所有最优路径,注意把已经遍历过的边流量清空。
另外,《训练指南》的题解指出用拆点法建模求最大流,不断在之前的残量网络追加分层直到最大流满足要求,这是通用解法,不过效率低一点。
AC 代码
#include <iostream>
#include <cstring>
#include <algorithm>
using namespace std;
#define M 202
#define N 51
struct edge {int u, v, cap, flow, cost;} e[M<<2]; struct node {int i, j, n;} f[2][N];
int path[N][N], g[N][N*M<<2], q[N], a[N], p[N], d[N], cnt[N], c, k, m, n, s, t; bool vis[N];
void add_edge(int u, int v, int cap, int cc) {
e[c].u = u; e[c].v = v; e[c].cap = cap; e[c].flow = 0; e[c].cost = cc; g[u][cnt[u]++] = c++;
e[c].u = v; e[c].v = u; e[c].cap = 0; e[c].flow = 0; e[c].cost = -cc; g[v][cnt[v]++] = c++;
}
bool cmp(int i, int j) {
return d[i] < d[j];
}
void print(int m, int t) {
cout << t << endl;
int c[] = {0, 0}, z = m;
for (int i=0; i<m; ++i) f[0][c[0]++] = {p[i], 0, i+1};
for (int i=0; i<t; ++i) {
node (&r)[N] = f[i&1], (&x)[N] = f[~i&1]; int &c1 = c[i&1], &c2 = c[~i&1] = 0;
cout << c1;
for (int j=0; j<c1; ++j) {
node &e = r[j];
cout << ' ' << e.n << ' ' << path[e.i][e.j];
if (++e.j < d[e.i]) x[c2++] = {e.i, e.j, e.n};
}
cout << endl;
for (int j=0; j<m && z<k; ++j) if (d[p[j]] < t-i) x[c2++] = {p[j], 0, ++z};
}
}
void solve() {
memset(cnt, c = 0, sizeof(cnt));
while (m--) {
int u, v; cin >> u >> v;
if (u == s || v == t) {
add_edge(u, v, 1, 1);
} else if (v == s || u == t) {
add_edge(v, u, 1, 1);
} else add_edge(u, v, 1, 1), add_edge(v, u, 1, 1);
}
while (true) {
memset(d, 1, sizeof(d)); memset(vis, 0, sizeof(vis));
d[s] = 0; q[0] = s; a[s] = N;
int head = 0, tail = 1;
while (head < tail) {
short u = q[head++]; vis[u] = false;
for (short i=0; i<cnt[u]; ++i) {
const edge& ee = e[g[u][i]];
if (ee.cap > ee.flow && d[ee.v] > d[u]+ee.cost) {
d[ee.v] = d[u]+ee.cost;
p[ee.v] = g[u][i];
a[ee.v] = min(a[u], ee.cap-ee.flow);
if (!vis[ee.v]) vis[q[tail++] = ee.v] = true;
}
}
}
if (d[t] >= N) break;
for (short u=t; u!=s; u=e[p[u]].u) {
e[p[u]].flow += a[t];
e[p[u]^1].flow -= a[t];
}
}
for (int i=m=0; i<cnt[s]; ++i) if (e[g[s][i]].flow > 0) {
d[m] = 1; int u = path[m][0] = e[g[s][i]].v;
while (u != t) for (int j=0; j<cnt[u]; ++j) if (e[g[u][j]].flow > 0) {
e[g[u][j]].flow = 0; u = path[m][d[m]++] = e[g[u][j]].v; break;
}
p[m] = m; ++m;
}
sort(p, p+m, cmp);
for (int i = 0, dd = d[p[0]], kk = 0; ; kk += (d[p[i]] - dd) * i, dd = d[p[i]]) {
while (i < m && d[p[i]] == dd) ++i;
if (i==m || (kk + (d[p[i]] - dd) * i) >= k) return print(min(i,k), dd-1 + (k-kk+i-1)/i);
}
}
int main() {
ios::sync_with_stdio(false); cin.tie(0); cout.tie(0);
while (cin >> n >> m >> k >> s >> t) solve();
return 0;
}