Linking
题意:
一共有
n
n
n 个点,现在用
n
2
n^2
n2 矩阵给出了任意两点之间的最短距离
a
i
j
a_{ij}
aij。
现需要你建几条边,使得所给出的任意两点的最短距离都要满足。
输出所建边权之和的最小值。如果无法满足,输出-1。
思路:
这道题与众不同的地方在于,已经给出了任意两点的最短距离,只需要判断是否仍存在更短距离(无解情况)或者能由其他路径能够到达(这两个点之间就不用建边)。
其实就是从一个完全图中,筛掉几个两点间的边,但仍要满足最短距离。
那么就可以用
f
l
o
y
d
floyd
floyd 来判断,是否存在一个中转点,能够连接其他路径使得当前两点间的距离就是所给的最短距离。如果是,那么当前两点间的直接连边就不需要建,成功的筛掉一个边。
这条边用不到了,因为任何可能用到这条边的路径,这条边都可以由另一条距离仍是最短距离的路径所代替,这条边就没必要存在了。
所以只跑一遍 Floyd 便可以解决问题。
需要注意的点:
-
在跑Floyd的时候,如果判断到有一个边可以由其他路径代替,需要先把这个边标记下来,最后再处理,而不是直接减去这个边权。因为一条边可能有多条替代路径,每次都减就重复了。
-
Floyd更新的时候,如果 k 和 i 相等或者 k 和 j 相等了,那么就相当于用一条边来更新比较一条边,这样的话肯定是相等的,但不是由其他路径来代替的。所以这种情况不满足,需要跳过。
和队友讨论的做法是这样的:
先将所有的边按照边权从小到大排序,遍历一遍。
- 如果当前边的两个端点是可通的,并且距离就是最短距离,那么这个边就没必要加,跳过。
- 如果不是,那么就用把这个边加上,用这两个点分别作为中转点更新其他所有点。
这里用到了一个小技巧,就是加上一个点没必要重新跑一遍Floyd,三种循环。因为只是这一个点改变了,那么只需要用这个点作为中转点更新其余所有与其相连的点就好。这样便只需要两种循环。
同理如果改变了一个边(注意只能把这个边权改小,不能改大!因为改大之后,其余之前由小边权更新的点回不去了,改小的话还能够更新其他点),只需要把两个端点分别作为中转点来更新与其相连的点就好了。
虽然这样做复杂度还是超,但是和队友一起回顾了Floyd的这个技巧,也是一件值得的事!
Code:
正解:
const int N = 5010, mod = 1e9+7;
int T, n, m;
int a[N];
int dist[N][N];
int f[N][N];
signed main(){
cin>>n;
int ans=0;
for(int i=1;i<=n;i++)
for(int j=1;j<=n;j++){
cin>>dist[i][j];
if(j>i) ans+=dist[i][j];
}
int flag=0;
for(int k=1;k<=n;k++)
{
for(int i=1;i<=n;i++)
{
for(int j=i+1;j<=n;j++)
{
if(i==k || j==k) continue;
if(dist[i][j] > dist[i][k]+dist[k][j]) flag=1;
else if(dist[i][j] == dist[i][k]+dist[k][j]) f[i][j]=1;
}
}
}
for(int i=1;i<=n;i++)
for(int j=i+1;j<=n;j++)
if(f[i][j]) ans-=dist[i][j];
if(flag) cout<<-1;
else cout<<ans;
return 0;
}
和队友讨论的超时做法:
const int N = 250010, mod = 1e9+7;
int T, n, m;
struct ST{
int dis, x, y;
}a[N];
int dist[510][510];
int d[510][510];
bool cmp(ST a, ST b){
return a.dis<b.dis;
}
void floyd(int k)
{
for(int i=1;i<=n;i++)
{
if(dist[i][k]>=0x3f3f3f3f) continue;
for(int j=1;j<=n;j++)
{
if(dist[i][k]+dist[k][j]<dist[i][j])
dist[i][j]=dist[i][k]+dist[k][j], dist[j][i]=dist[i][j];
}
}
}
signed main(){
Ios;
cin>>n;
int cnt=0;
for(int i=1;i<=n;i++){
for(int j=1;j<=n;j++){
int x;cin>>x;
if(j>i) a[++cnt]={x,i,j};
}
}
mem(dist,0x3f);
sort(a+1,a+cnt+1,cmp);
int ans=0, flag=0;
for(int i=1;i<=cnt;i++)
{
int dis=a[i].dis, x=a[i].x, y=a[i].y;
if(dist[x][y] == dis) continue;
else if(dist[x][y] < dis) flag=1;
ans += dis;
dist[x][y] = dist[y][x] = dis;
floyd(x), floyd(y);
}
if(flag) cout<<-1;
else cout<<ans;
return 0;
}
很好的一道图论题,考察了对Floyd的理解和运用。
Floyd并不是简单的用于求任意两点的最短路径!