题目大意:
有一个n(
n<=100
)个点m(
m<=3000
)条边的无向图,边权为1,求删掉每一条边之后,每个点到所有点的最短距离和。
多组数据
题目分析:
最暴力的方法就是删掉每一条边,然后枚举每一个点,跑一边bfs,时间复杂度O(nm^2)
据说会TLE诶O(∩▽∩)O
于是看了大家的博客,知道了有最短路径树这种东西……
最短路径树就是对于一个源点,到每一个点都有一个最短路径,转移到这个点的点设为这个点的父亲,然后我们就有了一颗以源点为跟的树。
这颗树的每一条边都是源点到其他点的最短路径上的一条边。
也就是说删掉的边如果不是最短路径树上的边,那么对这个点到所有点的距离都没有影响。
那么我们先处理出每一个点的最短路径树。
删掉每一条边之后,枚举每一个点作为源点,如果删掉的这条边不是最短路径上的点,那就直接加上预处理出的答案,如果是,就重新跑一遍最短路。
只有当一条边在最短路径树上时会重新计算,每棵最短路径树有n条边,有n棵最小路径树,所以一共会重新计算n^2次。时间复杂度(n^2m)
代码如下:
#include <cstdio>
#include <cstring>
#include <iostream>
#define N 120
#define M 12000
using namespace std;
int n,m,tmp;
int fa[N][N];
int fir[N],nes[M],v[M],tot=1;
int sum[N],dis[N];
bool pc[M],judge;
int dl[N],X[M],Y[M];
void edge(int x,int y)
{
v[++tot]=y;
nes[tot]=fir[x];
fir[x]=tot;
return;
}
#define edge(x,y) edge(x,y),edge(y,x)
int bfs(int S)
{
memset(dis,-1,sizeof(dis));
int l=1,r=1;
dis[S]=0; dl[1]=S;
fa[S][S]=0;
static int c;
int sum=0;
while(l<=r)
{
c=dl[l++];
sum+=dis[c];
for(int t=fir[c];t;t=nes[t])
{
if(~dis[v[t]] || pc[t]) continue;
dis[v[t]]=dis[c]+1;
fa[S][v[t]]=c;
dl[++r]=v[t];
}
}
return r==n?sum:-1;
}
void init()
{
tot=1;
judge=true;
memset(fir,0,sizeof(fir));
memset(sum,0,sizeof(sum));
for(int i=1;i<=m;i++)
{
scanf("%d%d",&X[i],&Y[i]);
edge(X[i],Y[i]);
}
for(int i=1;i<=n;i++)
if((sum[i]=bfs(i))==-1)
{
judge=false;
break;
}
}
int main()
{
while(scanf("%d%d",&n,&m)!=EOF)
{
init();
for(int i=1;i<=m;i++)
{
if(!judge) printf("INF\n");
else
{
int ans=0;
pc[i<<1]=true; pc[i<<1|1]=true;
for(int j=1;j<=n;j++)
{
if(fa[j][X[i]]!=Y[i] && fa[j][Y[i]]!=X[i]) ans+=sum[j];
else
{
if(~(tmp=bfs(j))) ans+=tmp;
else
{
ans=-1;
break;
}
}
}
if(~ans) printf("%d\n",ans);
else printf("INF\n");
pc[i<<1]=false; pc[i<<1|1]=false;
}
}
}
return 0;
}