题目:http://codeforces.com/contest/400/problem/D
N个点,M条边,每条边有一个权值x,x可以为0。然后把这些点分成K组,给一个序列C,共K个数,C[i]>=1,按照编号1~N,1~C[1]为第一组,C[1]+1~C[2]为第2组,以此类推。
然后问每一组的点是否到同组其他点的最短路都是0,是输出Yes,否则输出No。
对于Yes的情况,还要输出一个K*K的矩阵D,D[i][j]代表第i组点到第j组点的最短距离,不存在路径则输出-1。
对于第一个判断,用并查集可以解决。如果两个点之间的边的权值是0,就给它们连一条边。全部边读取完之后判断同一组的点的祖先是否相同即可。
在前面读取边的时候,还可以同时更新两组之间的最短路径,然后就跑一下弗洛伊德算法就OK了。
#include<cstdio>
#include<cstring>
int n, m, q;
int i, j, k;
int c[501];//记录当前组的点数
int s[501];//记录当前组第一个点的编号
int f[100001];//并查集祖先
int p[100001];//记录点所在组
int d[501][501];
int x, y, z;
//并查集查找祖先
int find(int xx){
int yy=xx;
for(;xx!=f[xx];xx=f[xx]);
return f[yy]=xx;
}
int main(){
while(~scanf("%d %d %d", &n, &m, &q)){
for(i=1; i<=n; i++) f[i]=i;
s[0]=1;
c[0]=0;
memset(d,-1,sizeof(d));
for(i=1; i<=q; i++){
d[i][i]=0;
scanf("%d", c+i);
s[i]=s[i-1]+c[i-1];
for(j=s[i]; j<s[i]+c[i]; j++) p[j]=i;
}
while(m--){
scanf("%d %d %d", &x, &y, &z);
int px = find(x);
int py = find(y);
if(px!=py && (!z)) f[px]=py;
//将路径信息更新到组的距离上
x = p[x];
y = p[y];
if(d[x][y]==-1 || z<d[x][y]){
d[x][y]=d[y][x]=z;
}
}
//判断同组的点是否同一祖先
bool flag=1;
for(i=1; i<=q; i++){
k = find(s[i]);
for(j=s[i]+1; j<s[i]+c[i]; j++){
if(find(j)!=k){
flag=0;
break;
}
}
if(!flag) break;
}
if(!flag) puts("No");
else{
puts("Yes");
//弗洛伊德算法
for(k=1; k<=q; k++){
for(i=1; i<=q; i++){
for(j=1; j<=q; j++){
if(d[i][k]==-1 || d[k][j]==-1) continue;
int tmp = d[i][k]+d[k][j];
if(d[i][j]==-1 || tmp<d[i][j]) d[i][j]=tmp;
}
}
}
for(i=1; i<=q; i++){
printf("%d", d[i][1]);
for(j=2; j<=q; j++) printf(" %d", d[i][j]);
puts("");
}
}
}
return 0;
}