样例:
样例1:
输入
4 5 2 4
1 2 1
2 3 5
1 4 3
4 2 2
4 3 4
输出:
1
样例2:
输入:
4 6 2 4
1 2 1
2 3 5
1 3 3
4 1 3
4 2 3
4 3 2
输出:
2
题意:给你一个有向图,然后还有两个节点x, y,让你通过反转图上的有向边使这x能到y,问反转有向边边权最大值的最小值
这个题有两种思路,一种用Dijkstra做,另一种用二分答案做。
Dijkstra做法:
思路:
Dijkstra是求最短路的算法,我们只需要改一改dis数组的用法(含义)就行了,在Dijkstra算法中,dis数组存的是最短路径,那么这里就让他存最大的边权,然后最小化最大的边权就是答案。
首先输入的时候相当于存一个无向图,我们让没反转之前的边为0,反转之后的边的有权值,就是我们输入的那个边的权值。然后就是跑Dijkstra算法了。这里有些改动,我们只需在之前更新点到点最短距离的时候换成更新点到点的最长边权就行了.
for (int i = head[u]; ~i; i = ne[i])
{
int v = e[i];
if (d[v] > d[u] + w[i])
{
d[v] = d[u] + w[i];
q.push({ d[v],v });
}
}
变成这个样子就行了
for (int i = head[u]; ~i; i = ne[i])
{
int v = e[i];
if (d[v] > max(d[u], w[i]))
{
d[v] = max(d[u], w[i]);
q.push({ d[v],v });
}
}
具体代码如下:
#define _CRT_SECURE_NO_WARNINGS
#include <bits/stdc++.h>
#include <unordered_map>
using namespace std;
const int N = 2e6 + 10;
const int NN = 3e3 + 10;
typedef long long ll;
const int null = 0x3f3f3f3f;
typedef unsigned long long ull;
typedef pair<ll, ll> pll;
int head[N], e[N], w[N], ne[N], idx = 0, ans = N, vis[N], n, m, d[N], cnt, x, y;
void add(int x, int y, int val)//链式前向星
{
e[idx] = y;
w[idx] = val;
ne[idx] = head[x];
head[x] = idx++;
}
void dijkstra()
{
for(int i = 0; i <= n; i ++)
{
d[i] = 1e10;
}
priority_queue<pll, vector<pll>, greater<pll>>q;
q.push({ 0,x });
d[x] = 0;
while (q.size())
{
pll temp = q.top();
q.pop();
int u = temp.second;
if (vis[u])
continue;
vis[u] = 1;
for (int i = head[u]; ~i; i = ne[i])//遍历与u相邻的点v
{
int v = e[i];
if (d[v] > max(d[u], w[i]))//如果x--v路径上最大的边权大于 x--u , u--v路径上的最大边权的话就更新
{
d[v] = max(d[u], w[i]);
q.push({ d[v],v });
}
}
}
}
void solve()
{
memset(head, -1, sizeof head);
cin >> n >> m >> x >> y;
int u, v, w;
for (int i = 1; i <= m; i++)
{
cin >> u >> v >> w;
add(u, v, 0);
add(v, u, w);
}
dijkstra();
cout << d[y];
}
int main()
{
int t;
t = 1;
while (t--)
{
solve();
}
return 0;
}
第一种做法结束
-------------------分割线--------------------------------------分割线-------------------------------------分割线--------
第二种做法:二分答案
思路:
二分 0 - 1e9 之前的数,寻找答案,最后输出答案。
寻找答案的过程:假设,那我们在建图的时候遇到权值小于等于mid的时候就反转这条边,反之就不反转。这里的反战可以添加一个反向的边,因为之前没反转的边不影响后面的结果。能到达的还是到达,到达不了的还是到达不了。然后建图之后,就用dfs搜索判断x 和 y是否连通,如果连通的说明还能小就让 r 左移,反之就让 l 右移。
具体细节详见代码:
#define _CRT_SECURE_NO_WARNINGS
#include <bits/stdc++.h>
#include <unordered_map>
using namespace std;
const int N = 2e6 + 10;
const int NN = 3e3 + 10;
typedef long long ll;
const int null = 0x3f3f3f3f;
typedef unsigned long long ull;
int n, m, x, y;
int from[N], to[N], val[N];
vector<int> vc[N];
int vis[N];
inline void dfs(int u)
{
if(vis[u])//如果之前遍历过就不管了
return;
vis[u] = 1;
for(auto it : vc[u])//向下深搜
{
if(!vis[it])
{
dfs(it);
}
}
}
inline bool check(int k)
{
for(int i = 1; i <= n; i ++)//全部清零,重新建图
{
vc[i].clear();
}
for(int i = 0; i < m; i ++)//建图过程
{
vc[from[i]].push_back(to[i]);
if(val[i] > k)//大了就不反转
continue;
vc[to[i]].push_back(from[i]);
}
for(int i = 0; i <= n; i ++ ) vis[i] = 0;//标记清零
dfs(x);//深搜寻找连通的点
if(vis[y]) return true;//判断x--y是否连通
else return false;
}
void solve()
{
cin >> n >> m >> x >> y;
for(int i = 0; i < m; i ++)
{
cin >> from[i] >> to[i] >> val[i];
}
int l = 0, r = 1e9;
while(l < r)
{
int mid = l + r >> 1;
if(check(mid))//成立
r = mid;
else l = mid + 1;
}
cout << l << "\n";
}
int main()
{
int t;
t = 1;
while (t--)
{
solve();
}
return 0;
}
最后这个第二种做法虽然用了内联函数优化一下时间,但是还是容易超时,所以这里要开O(2)优化,要不然的会T掉。
(上述两种思路均是来源于实验室两位大佬的思路QWQ)
THE END