二分枚举最短路。
我是服了这几个词了,你是真的牛批:
- unidirectional 单向
- bidirectional 双向
- undirected 无向
- directed 有向
和HDU 2363很像,注意体会。
给你一个无向图,让你找一个从1
到N
的最小找最大(每条路上最小的边权(cap
),所有路上取其最大值),然后还必须这条路的另外一个边权(time
)之和小于等于一个常数T
。
就像上面那道题的二分那样,这道题是对这个全局最优的容量边权进行二分,我们知道这条路上的其他边的容量肯定都大于等于这个值。所以,就可以根据这个【边限制】来限制当前的图,然后再判断当前图的可行性从而决定下一次二分取左半边还是右半边。
这里比较抽象,所谓判断当前图的可行性,就是看起点和终点在当前限制下连不连通。然而,这道题要求的第二层条件不是仅仅可行就行(这个一定是隐含条件),而是边权time
之和小于等于T
,所以就必须要用最短路算法了,因为判断这个条件可以转化为判断最短路是否小于等于T
(当然你可以再多走几条边,但是我只要保证下限(最短路的值)刚好满足了上限T
就行)。所以这个最短路算法要完成两个功能,执行上面二分的边限制(以cap
为边权)以及求解当前图的最短路(以time
为边权)。
然后再说说这个二分的具体细节。像我这两道题写的这个二分,应用条件是找从左向右第一个满足条件的值(是具体的值,不是下标,所以重复无所谓),所谓满足条件至少是指当前限制下起点终点连通。所以,这个二分区间的排序要符合【越往左越优】。另外,按照这个二分写法,如果取了右半边后二分刚好结束(l=r
)那么最短路的值可能还是INF
,所以要再来一次最短路算法,但这道题不用这样,因为这道题不用输出最短路的值。
另外,想一想为什么能保证最后全局最优的那个边一定被最短路走?道理很简单。(其实,在二分的中间过程中,当前作为中值分界点的边当然不一定被当前的最短路访问到,但是继续往左、限制越来越苛刻,到最后可以保证这一点。(想一想上一段的二分应用条件))
如果这道题仅仅是最小找最大(无向图),那么就可以像那个题一样直接并查集搞定了。。只不过顺序反一下就行。
#include <cstdio>
#include <iostream>
#include <algorithm>
#include <vector>
#include <cstring>
#include <string>
#include <queue>
using namespace std;
const int INF = 1e9;
const int MAXN = 10001;
int N, M, T, X;
struct Edge
{
int n, cap, time;
};
vector<Edge> ve;
vector<int> v[MAXN];
vector<int> vc;
int limit;
int d[MAXN];
bool vis[MAXN];
void init()
{
ve.clear();
for (int i = 1; i <= N; i++) v[i].clear();
vc.clear();
}
void spfa(int s) // 这题必须上spfa
{
queue<int> q;
fill(d + 1, d + N + 1, INF);
memset(vis, 0, sizeof vis);
q.push(s);
d[s] = 0;
vis[s] = true;
for (; !q.empty();)
{
int t = q.front();
q.pop();
vis[t] = false;
for (int i = 0; i < v[t].size(); i++)
{
int n = ve[v[t][i]].n;
int cap = ve[v[t][i]].cap;
int time = ve[v[t][i]].time;
if (cap >= limit) 筛选边!
{
if (d[t] + time < d[n])
{
d[n] = d[t] + time;
if (!vis[n])
{
q.push(n);
vis[n] = true;
}
}
}
}
}
}
bool cmp(const int& a, const int& b)
{
return a > b;
}
int main()
{
int a, b, c, dd;
scanf("%d", &X);
for (; X--;)
{
scanf("%d%d%d", &N, &M, &T);
init();
for (int i = 0; i < M; i++)
{
scanf("%d%d%d%d", &a, &b, &c, &dd);
ve.push_back(Edge{ b,c,dd });
ve.push_back(Edge{ a,c,dd });
v[a].push_back(i << 1);
v[b].push_back(i << 1 | 1);
vc.push_back(c);
}
sort(vc.begin(), vc.end(), cmp); // 这道题,用dijkstra会WA,二分没整好必然WA,这里是从大到小排序!
int l = 0, r = M - 1, mid;
for (; l < r;)
{
mid = (l + r) >> 1;
limit = vc[mid];
spfa(1);
if (d[N] == INF || d[N] > T) l = mid + 1; // 由于除法向下去整,不可 l=mid!!
else r = mid;
}
printf("%d\n", vc[l]);
}
return 0;
}
// 《算法笔记》P128 以后就把二分想清楚了!cap必须从大到小排才符合规矩!
// 找cap序列中相应d[N]第一个小于等于T的位置!!