求两条最短路最多重叠的点数。
给一个无向图,再给两对起点终点,每一对起点终点之间都可能有多条最短的路,设s1
和t1
的最短路是r1
,s2
和t2
的最短路是r2
,求r1
和r2
最多能相互重叠多少个点。
这道题思想很巧妙,涉及了dp的思想。首先想明白一个问题,两条最短路最多的重叠部分必然是连续的,不可能有两段或更多段分散的重叠部分。为什么?反证法,若有的话,则某两段重叠部分之间的各自的分散部分一定可以都采用更小的那段分散部分。
建立一个数组cnt[][]
记录全图的两点之间的最短路最多可以有多少条边,使用floyd
算法,在g[i][k]+g[k][j] < g[i][j]
和g[i][k]+g[k][j] == g[i][j]
两种情况下对cnt[i][j]
进行计算。然而,还要对cnt[][]
数组进行初始化,很直接的想法就是初始边的两点设为1
,没有边的两点为0
。但是,这里有一个坑!输入数据中实锤有自环! 自环意味着cnt[x][x]
也被设为了1
,但是这是不符合定义的,除非你这个自环的权值是非正数,但是根据题意,权值的实际意义是路的长度,都是正值。(如果是非正的话这题也就GG了,有可达的零环意味着可以在这个零环上走到死,使得重叠的点数无穷大,有可达的负环意味着你连最短路都求不出来)
说这么多就很简单,当a
,b
两点之间有边时,要在cnt[a][b] = cnt[b][a] = 1
之前加上条件a != b
。
接下来提一下上述情况具体运行到哪里出了问题。
答案:当k==i
或者k==j
时,在floyd
里的cnt[i][j] < cnt[i][k] + cnt[k][j]
这个条件下引发了错误判断,继而计算了错误值。(想一想我在这个里面提过的三个情况)
证据:把输入边时的a!=b
条件去掉,在floyd
的此处加上条件&&(k!=i)&&(k!=j)
,也AC了。
最后,枚举每两个点,判断这两点之间的最短路能不能同时成为s1
,t1
和s2
,t2
的最短路的一部分,这个判断的写法很巧妙,和cnt[][]
没关系。若可以的话我们再拿这两点cnt[][]
的值更新ans
。(虽然这两点之间可能有多条最短路,但是这些最短路都是首尾一致的,可以随意替换,于是我们都拿cnt[][]
最大的那个当作子路)
(4月9日更新:其实这个题只要在floyd
里面让i
,j
,k
各不相等就行了)
#include <cstdio>
#include <iostream>
#include <algorithm>
#include <vector>
#include <cstring>
#include <string>
#include <queue>
using namespace std;
const int INF = 1e9;
const int MAXN = 301;
int N, M;
int g[MAXN][MAXN];
int cnt[MAXN][MAXN]; // 两点的所有最短路上最多有几条边
int ans;
void init()
{
for (int i = 1; i <= N; i++)
{
for (int j = i; j <= N; j++)
{
if (i == j) g[i][j] = 0;
else g[i][j] = g[j][i] = INF;
}
}
memset(cnt, 0, sizeof cnt);
ans = -1;
}
int main()
{
int a, b, c, d;
for (; ~scanf("%d%d", &N, &M);)
{
if (!N && !M) break;
init();
for (int i = 0; i < M; i++)
{
scanf("%d%d%d", &a, &b, &c);
g[a][b] = g[b][a] = min(g[a][b], c);
if (a != b) // 如果来 X X Y 的数据怎么办? (自环)(Y肯定大于0)
cnt[a][b] = cnt[b][a] = 1; // 必须始终让cnt[x][x]=0,无论有没有自环,因为永远都不能走这个自环!
}
scanf("%d%d%d%d", &a, &b, &c, &d);
for (int k = 1; k <= N; k++)
{
for (int i = 1; i <= N; i++)
{
for (int j = 1; j <= N; j++)
{
if (g[i][k] != INF && g[k][j] != INF && g[i][k] + g[k][j] < g[i][j])
{
g[i][j] = g[i][k] + g[k][j];
cnt[i][j] = cnt[i][k] + cnt[k][j];
}
else if (g[i][k] + g[k][j] == g[i][j] && cnt[i][j] < cnt[i][k] + cnt[k][j])
{
cnt[i][j] = cnt[i][k] + cnt[k][j];
}
}
}
}
for (int i = 1; i <= N; i++)
{
for (int j = 1; j <= N; j++)
{
if (cnt[i][j] > ans)
{
if (g[a][b] == g[a][i] + g[i][j] + g[j][b] && g[c][d] == g[c][i] + g[i][j] + g[j][d])
{ // 表示i,j的最短路至少可以是a,b其中一条最短路的子路
ans = cnt[i][j];
}
}
}
}
printf("%d\n", ans + 1); // 可以证明两条最短路的重叠部分一定是连续的,所以再加1等于重叠的点数
}
return 0;
}