找一条点权极值相差最小的路,在此基础上找最短路。
下面的代码思想是对差值进行二分,因为题目要求差值最小。差值最小是0,差值最大是输入的最大值减最小值。我们取某个差值作为限制条件,拿这个差值来限制最短路算法寻找下一个点的过程。
我想到的一种方法没有AC,而且至今没有想出问题所在,是这样的,把差值确定,但是具体的height
上下限不确定,这样去限制SPFA
算法,怎么在最短路算法中判断当前进行的路的差值呢?给每个点安排两个值,__min[]
和__max[]
,存放从起点走到当前点的最短路的极值,每当找下一个点时,判断下一个点的height
是否会破坏中介点存放的历史道路的差值。要说明的是,差值确定的意思是小于等于差值即可,所以某个差值不连通的话,更小的差值就不用试了。另外,考虑这种方法是因为觉得__min[]
和__max[]
的值是随着算法过程而递减和递增的,所以可以中途判断。
然而没有AC,所以肯定有问题。。感觉大概率是不能这么直接用一个差值来限制算法,判断传递过程可能存在逻辑错误,然而不知道测试数据也懒得想反例。
正确的方法是不仅要有差值,还要给定一个下限,这样上限也确定了,范围就确定了,下限就枚举每个点的height
(正确答案肯定是以某个点的height
为底),这样每次二分就要枚举N
次SPFA
,得到一个同差值不同上下限的最小d[N]
;多次二分得到全局最优。因为有了上下限,所以最短路算法里直接可以根据height
排除下一个点了,逻辑也就比较清晰可证了,没再出什么幺蛾子。
下面放上正确代码,有几处还是要注意的,已注释。
#include <cstdio>
#include <iostream>
#include <algorithm>
#include <vector>
#include <cstring>
#include <string>
#include <queue>
using namespace std;
const int INF = 1e9;
const int MAXN = 101;
int N, M, T;
struct Edge
{
int n, w;
};
vector<Edge> ve;
vector<int> v[MAXN];
int height[MAXN];
int d[MAXN];
bool inq[MAXN];
int _min, _max;
int diff;
int ans;
int pre[MAXN];
void init()
{
_min = INF;
_max = -1;
ve.clear();
for (int i = 1; i <= N; i++) v[i].clear();
memset(pre, 0, sizeof pre);
diff = ans = 0; // 这句防止起点等于终点的极端情况,不然这两个都没有被显式赋初值就被输出去了
}
void spfa(int s, int low, int high) // 只是定了一个明确的上下限
{
queue<int> q;
fill(inq + 1, inq + N + 1, false);
fill(d + 1, d + N + 1, INF);
if (height[s] < low || height[s] > high) return; // 先初始化完d[]再判断,因为调用返回后使用d[]的值
q.push(s);
inq[s] = true;
d[s] = 0;
for (; !q.empty();)
{
int t = q.front();
q.pop();
inq[t] = false;
for (int i = 0; i < v[t].size(); i++)
{
int n = ve[v[t][i]].n;
int w = ve[v[t][i]].w;
if (height[n] < low || height[n] > high) continue;
if (d[t] + w < d[n])
{
d[n] = d[t] + w;
pre[n] = t;
if (!inq[n])
{
q.push(n);
inq[n] = true;
}
}
}
}
}
void route(int s)
{
if (s == 1)
{
cout << s;
return;
}
route(pre[s]);
cout << "->" << s;
}
int main()
{
int a, b, c;
scanf("%d", &T);
for (; T--;)
{
scanf("%d%d", &N, &M);
init();
for (int i = 1; i <= N; i++)
{
scanf("%d", &height[i]);
_min = min(_min, height[i]);
_max = max(_max, height[i]);
}
for (int i = 0; i < M; i++)
{
scanf("%d%d%d", &a, &b, &c);
ve.push_back(Edge{ b,c });
ve.push_back(Edge{ a,c });
v[a].push_back(i << 1);
v[b].push_back(i << 1 | 1);
}
int l = 0, r = _max - _min; // 差值最小和最大,对差值二分
for (; l <= r;) // 这里取等号,l和r刚刚相等时,若上一次循环取了右半边(ans==INF),则还要再来一次枚举才能使diff和ans得到最后正确的值
{
diff = (l + r) >> 1;
ans = INF;
for (int i = 1; i <= N; i++)
{
spfa(1, height[i], height[i] + diff);
ans = min(ans, d[N]);
}
if (l == r) break; // 所以这句放的位置非常重要,不放则无限循环,放到底下则上一次循环就退出了(右半边情况还得再来一次)
if (ans != INF) r = diff;
else l = diff + 1;
}
printf("%d %d\n", diff, ans);
//route(N);
}
return 0;
}
然后是自己错误的代码。。
#include <cstdio>
#include <iostream>
#include <algorithm>
#include <vector>
#include <cstring>
#include <string>
#include <queue>
using namespace std;
const int INF = 1e9;
const int MAXN = 101;
int N, M, T;
struct Edge
{
int n, w;
};
vector<Edge> ve;
vector<int> v[MAXN];
int height[MAXN];
int d[MAXN];
bool inq[MAXN];
int _min, _max; // 全局最小最大
int limit;
int __min[MAXN];
int __max[MAXN];
int pre[MAXN];
void init()
{
_min = INF + 1;
_max = -1;
ve.clear();
for (int i = 1; i <= N; i++) v[i].clear();
memset(pre, 0, sizeof pre);
}
void spfa(int s)
{
queue<int> q;
fill(inq + 1, inq + N + 1, false);
fill(d + 1, d + N + 1, INF);
q.push(s);
inq[s] = true;
d[s] = 0;
fill(__min + 1, __min + N + 1, INF + 1);
fill(__max + 1, __max + N + 1, -1);
__min[s] = __max[s] = height[s];
for (; !q.empty();)
{
int t = q.front();
q.pop();
inq[t] = false;
for (int i = 0; i < v[t].size(); i++)
{
int n = ve[v[t][i]].n;
int w = ve[v[t][i]].w;
if (max(__max[t], height[n]) - min(__min[t], height[n]) <= limit)
{
if (d[t] + w < d[n])
{
__max[n] = max(__max[t], height[n]);
__min[n] = min(__min[t], height[n]);
d[n] = d[t] + w;
pre[n] = t;
if (!inq[n])
{
q.push(n);
inq[n] = true;
}
}
}
}
}
}
void route(int s)
{
if (s == 1)
{
cout << s;
return;
}
route(pre[s]);
cout << "->" << s;
}
int main()
{
int a, b, c;
scanf("%d", &T);
for (; T--;)
{
scanf("%d%d", &N, &M);
init();
for (int i = 1; i <= N; i++)
{
scanf("%d", &height[i]);
_min = min(_min, height[i]);
_max = max(_max, height[i]);
}
for (int i = 0; i < M; i++)
{
scanf("%d%d%d", &a, &b, &c);
ve.push_back(Edge{ b,c });
ve.push_back(Edge{ a,c });
v[a].push_back(i << 1);
v[b].push_back(i << 1 | 1);
}
int l = 0, r = _max - _min, mid; // 差值最小和最大
for (; l < r;) // 对差值二分
{
mid = (l + r) >> 1;
limit = mid;
spfa(1);
if (d[N] != INF) r = mid;
else l = mid + 1;
}
limit = l;
spfa(1);
printf("%d %d\n", l, d[N]);
//route(N);
}
return 0;
}
再然后,在网上又看到一种方法,以(N^2)/2
枚举每两个点的height
,然后根据高度差排序,然后从小到大依次进行上下限确定的最短路算法,但是我觉得我看到的代码还有点问题,因为没考虑高度差相同但上下限不同的情况,这个时候我们要取多次最短路算法后的最小值。