农夫约翰正在制造一台新的挤奶机,并希望对这件事进行严格保密。
他将挤奶机藏在了农场的深处,使他能够在不被发现的情况下,进行这项任务。
在机器制造的过程中,他要在牛棚和挤奶机之间一共进行 T 次往返。
他有一个秘密通道只能在返程时使用。
农场由 N 个地标(编号 1∼N)组成,这些地标由 P 条双向道路(编号 1∼P)连接。
每条道路的长度为正,且不超过 106106。
多条道路可能连接同一对地标。
为了尽可能的减少自己被发现的可能性,农场中的任何一条道路都最多只能使用一次,并且他应该尝试使用尽可能短的道路。
帮助约翰从牛棚(地标 1)到达秘密挤奶机(地标 N)总共 T 次。
找到他必须使用的最长的单个道路的最小可能长度。
请注意,目标是最小化所使用的最长道路的长度,而不是最小化所有被使用道路的长度之和。
保证约翰可以在不走重复道路的情况下完成 T 次行程。
输入格式
第一行包含三个整数 N,P,T。
接下来 P 行,每行包含三个整数 Ai,Bi,Li,表示地标 Ai 和 Bi 之间存在一条长度为 Li 的道路。
输出格式
输出一个整数,表示约翰必须使用的最长的单个道路的最小可能长度。
数据范围
1≤T≤200
2≤N≤200
1≤P≤40000
1≤Ai,Bi≤N
1≤Li≤106
输入样例:
7 9 2
1 2 2
2 3 5
3 7 5
1 4 1
4 3 1
4 5 7
5 7 1
1 6 3
6 7 3
输出样例:
5
样例解释
约翰可以选择以下两条路径:1−2−3−71−2−3−7 和 1−6−71−6−7。
两条路径中包含的所有道路长度均不超过 55。
约翰无法在仅使用长度小于 55 的道路的情况下,两次从 11 到 77。
解析:
题目要求:求起点到终点的不相交路径中边长最长的边最小值。
二分:由于题目求的时最大值最小,因此偶先考虑二分。
二段性判定:二分边长,如果当前的边长 mid 时最终答案,及在不大于 mid 的边长的边中能找到 T 条路经,那么在大于 mid 的集合中一定包含正确答案的边的集合;又因为 mid 是答案,因此答案的边的集合一定含边长为 mid 的边,小于 mid 的边的集合一定不包含边长为 mid 的边。
可以使用最大流算法判断二分结果,及判断是否有 T 条不相交路径。
正反边均有流量:
由于最大流算法只能处理有向图,因此我们需要将无向图转换成有向图。每一条无向边等价于两条有向边,且流量均为 1。同时我们还可以将这两条有向边与残留网络中的反向边合并,正确性我们可以通过(1)流量守恒,(2) 容量限制,来判断。
可行流与不相交路径的关系:(问题转化的正确性判断)
可行流 ——>不相交路径:由于每条边的容量为 1,且算法实现是的容量最小单位就是 1,因此每条边只能使用一次,且类似欧拉路径的证明方法,每个出边一定对应每条路径。
不相交路径 ——>可行流:对于每条边,使用过就等价于流过 1 个单位的流量。
#include<iostream>
#include<string>
#include<cstring>
#include<cmath>
#include<ctime>
#include<algorithm>
#include<utility>
#include<stack>
#include<queue>
#include<vector>
#include<set>
#include<math.h>
#include<map>
#include<sstream>
#include<deque>
#include<unordered_map>
#include<unordered_set>
#include<bitset>
using namespace std;
typedef long long LL;
typedef unsigned long long ULL;
typedef pair<int, int> PII;
const int N = 2e2 + 10, M = 8e4 + 5, INF = 0x3f3f3f3f;
int n, m, k, S, T;
int h[N], e[M], f[M], w[M], ne[M], idx;
int q[N], d[N], cur[N];
void add(int a, int b, int c) {
e[idx] = b, w[idx] = c, ne[idx] = h[a], h[a] = idx++;
e[idx] = a, w[idx] = c, ne[idx] = h[b], h[b] = idx++;
}
bool bfs() {
int hh = 0, tt = 0;
memset(d, -1, sizeof d);
q[0] = S, d[S] = 0, cur[S] = h[S];
while (hh <= tt) {
int t = q[hh++];
for (int i = h[t]; i != -1; i = ne[i]) {
int j = e[i];
if (d[j] == -1 && f[i]) {
d[j] = d[t] + 1;
cur[j] = h[j];
if (j == T)return 1;
q[++tt] = j;
}
}
}
return 0;
}
int find(int u, int limit) {
if (u == T)return limit;
int flow = 0;
for (int i = cur[u]; i != -1 && flow < limit; i = ne[i]) {
int j = e[i];
cur[u] = i;
if (d[j] == d[u] + 1 && f[i]) {
int t = find(j, min(f[i], limit - flow));
if (!t)d[j] = -1;
f[i] -= t, f[i ^ 1] += t, flow += t;
}
}
return flow;
}
int dinic() {
int ret = 0, flow;
while (bfs())while (flow = find(S, INF))ret += flow;
return ret;
}
bool check(int mid) {
for (int i = 0; i < idx; i++) {
if (w[i] > mid)f[i] = 0;
else f[i] = 1;
}
int t = dinic();
//cout << "________________________________ " << t <<" "<<k << endl;
return t>= k;
}
int main() {
cin >> n >> m >> k;
memset(h, -1, sizeof h);
S = 1, T = n;
for (int i = 1,a,b,c; i <= m; i++) {
scanf("%d%d%d", &a, &b, &c);
add(a, b, c);
}
int l = 0, r = 1e8,mid;
while (l < r) {
mid = (l + r) >> 1;
//cout << "JJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJ "<<mid << endl;
if (check(mid))r = mid;
else l = mid + 1;
}
cout << r << endl;
return 0;
}