藏宝图
题目描述
Czy爬上黑红树,到达了一个奇怪的地方……
Czy发现了一张奇怪的藏宝图。图上有n个点,m条无向边。已经标出了图中两两之间距离dist。但是czy知道,只有当图刚好又是一颗树的时候,这张藏宝图才是真的。如果藏宝图是真的,那么经过点x的边的边权平均数最大的那个x是藏着宝物的地方。请计算这是不是真的藏宝图,如果是真的藏宝之处在哪里。
输入
输入数据第一行一个数T,表示T组数据。
对于每组数据,第一行一个n,表示藏宝图上的点的个数。
接下来n行,每行n个数,表示两两节点之间的距离。
输出
输出一行或两行。第一行”Yes”或”No”,表示这是不是真的藏宝图。
若是真的藏宝图,第二行再输出一个数,表示哪个点是藏宝之处。
样例输入
2
3
0 7 9
7 0 2
9 2 0
3
0 2 7
2 0 9
7 9 0
样例输出
Yes
1
Yes
3
样例解释:第一棵树的形状是1--2--3。1、2之间的边权是7,2、3之间是2。
第二棵树的形状是2--1--3。2、1之间的边权是2,1、3之间是7。
提示
对于30%数据,n<=50,1<=树上的边的长度<=10^9。
对于50%数据,n<=600.
对于100%数据,1<=n<=2500,除30%小数据外任意0<=dist[i][j]<=10^9,T<=5
【题解】
考试的时候完全没有思路,想试试把多余边都拆掉能不能行,但是完全没有想起“最小生成树”这几个字眼。其实想想最小生成树,加有用的边不就相当于删无用的边吗?还是得懂得变通啊。
稠密图卡克鲁斯卡尔是众所周知,而每两点之间都有边可算得上是最稠密不过的图了,复习普里姆,选择最近的点把它加入树。建出树之后dfs求各点到1的距离,lca求各两点之间距离,验证一下是否是树。是树不是树都好办,这题主要是步骤非常多,用了很多算法,调试有一定难度(我也不出意料地打得非常冗长)。思路上大概也只有最小生成树和稠密图比较困难吧。
#include<iostream> #include<cstdio> #include<cstring> #include<cmath> #define ll long long using namespace std; const int sj=2505; ll ca,n,dist[sj][sj],dis[sj],mi; int e,xb[sj],lca[sj][sj],f[sj],zx,h[sj],fa[sj]; bool r[sj],op; void init() { scanf("%d",&n); memset(dis,0x3f,sizeof(dis)); memset(f,-1,sizeof(f)); memset(h,-1,sizeof(h)); memset(r,0,sizeof(r)); for(int i=1;i<=n;i++) { for(int j=1;j<=n;j++) scanf("%lld",&dist[i][j]); } } struct B { int ne,v,w; }b[sj*2]; void add(int x,int y,int z) { b[e].v=y; b[e].ne=h[x]; b[e].w=z; h[x]=e++; } void mst() { memset(dis,0x3f,sizeof(dis)); for(int i=1;i<=n;i++) if(dist[i][1]<dis[i]) { dis[i]=dist[i][1]; fa[i]=1; } r[1]=1; for(int i=1;i<=n-1;i++) { mi=0x7fffffff; zx=1; for(int j=1;j<=n;j++) if(!r[j]&&dis[j]<mi) mi=dis[j],zx=j; r[zx]=1; add(fa[zx],zx,dis[zx]); add(zx,fa[zx],dis[zx]); for(int j=1;j<=n;j++) if(!r[j]&&dist[zx][j]<dis[j]) { dis[j]=dist[zx][j]; fa[j]=zx; } } } int find(int x) { if(f[x]==-1) return x; f[x]=find(f[x]); return f[x]; } void hb(int x,int y) { x=find(x); y=find(y); if(x!=y) f[x]=y; } void dfs(int x) { r[x]=1; for(int i=h[x];i!=-1;i=b[i].ne) if(!r[b[i].v]&&b[i].v!=fa[x]) { dis[b[i].v]=dis[x]+b[i].w; dfs(b[i].v); } } void tarjan(int x) { xb[x]=x; r[x]=1; for(int i=h[x];i!=-1;i=b[i].ne) if(!r[b[i].v]) { tarjan(b[i].v); hb(x,b[i].v); xb[find(b[i].v)]=x; } for(int i=1;i<=n;i++) if(r[i]) lca[i][x]=lca[x][i]=xb[find(i)]; } int main() { //freopen("t3.txt","r",stdin); //freopen("treas9.in","r",stdin); //freopen("treas.out","w",stdout); scanf("%d",&ca); for(int l=1;l<=ca;l++) { init(); mst(); memset(r,0,sizeof(r)); dfs(1); memset(r,0,sizeof(r)); tarjan(1); op=1; for(int i=1;i<=n;i++) for(int j=i+1;j<=n;j++) if(dis[i]+dis[j]-2*dis[lca[i][j]]!=dist[i][j]) { op=0; break; } if(!op) printf("No\n"); if(op) { printf("Yes\n"); double temp; int ge; mi=0; zx=1; for(int i=1;i<=n;i++) { ge=0; temp=0; for(int j=h[i];j!=-1;j=b[j].ne) { temp+=abs(dis[i]-dis[b[j].v]); ge++; } temp/=ge; if(temp>mi) { mi=temp; zx=i; } } printf("%d\n",zx); } } //while(1); return 0; }
南风知我意,吹梦到西洲。