VIJOS P1540 月亮之眼
【题目大意】
有多个珠子,给出部分珠子之间的相对上下位置和间距,问你这些珠子在满足给出的条件下,是否能把珠子排列在一条竖直直线上,如果能,求出每个珠子距离最高的珠子的距离,珠子的位置可重叠。
【分析】
可以根据珠子的位置关系建立一张有向图,A->B 为A比B高,权值为之间的距离。可以发现必须满足下列三种情况:
1、图有连通;无法比较出不同连通分支的上下关系。
2、有向图没有环;根据位置的传递关系,不可能自己比自己低。
3、如果从A到B有多条路径,路径的长度都应该一样;要不然B的位置关系就会有二义性。
我本来的想法是按顺序验证上面三条规则,把有向图转为无向图判联通,用拓扑排序判环,用DFS来判路径长度,想想太复杂了。后来想想其实没必要那么复杂。对于每条有向边,添加一条负权反向边,任意选定一个点,假设它是最高点,离最高点的距离是0,用DFS搜索可连向的点,如果新点未访问,则新点的距离就是当前点的距离加上边的权值,如果新点被访问过,这需要验证当前点的距离加上边权是否与新点原来的权值相等,如果不相等,则说明有冲突。如果DFS结束还有点没被访问,则说明图不连通。如果发现有点的距离为负数,则说明那个点比搜索开始的点的距离更高。
DFS的代码很简单:
1 #include <stdio.h> 2 #include <string.h> 3 struct node 4 { 5 int x,y,c,next; 6 void mset(int a,int b,int z,int nn) 7 { 8 x=a; 9 y=b;c=z; 10 next=nn; 11 } 12 }ev[5000]; 13 int ej,n,p; 14 int map[502]; 15 int vv[502][502]; 16 int num[502]; 17 int dis[502]; 18 bool ans; 19 int mmin; 20 int min(int a,int b){return a<b?a:b;} 21 void insert(int x,int y,int v) 22 { 23 int tem=map[x]; 24 ev[ej++].mset(x,y,v,tem); 25 map[x]=ej-1; 26 vv[x][y]=v; 27 vv[y][x]=-v; 28 } 29 void dfs(int x) 30 { 31 if (num[x]) return; 32 num[x]=1; 33 int p=map[x]; 34 while (p!=-1 && ans) 35 { 36 node tt=ev[p]; 37 int t2=dis[x]+tt.c; 38 if (dis[tt.y]!=-1 && dis[tt.y]!=t2) 39 { 40 ans=false; 41 return; 42 } 43 dis[tt.y]=t2; 44 dfs(tt.y); 45 p=tt.next; 46 } 47 } 48 int main() 49 { 50 ej=0;mmin=0; 51 ans=true; 52 //freopen("in.txt","r",stdin); 53 memset(map,-1,sizeof map); 54 memset(num,0,sizeof num); 55 memset(dis,-1,sizeof dis); 56 scanf("%d%d",&n,&p); 57 for (int i=0;i<p;++i) 58 { 59 int a,b,c; 60 scanf("%d%d%d",&a,&b,&c); 61 insert(a,b,c); 62 insert(b,a,-c); 63 num[b]=1; 64 } 65 int p=0; 66 for (int i=1;i<=n;++i) 67 if (num[i]==0) {p=i;break;} 68 memset(num,0,sizeof num); 69 if (p==0) 70 { 71 printf("-1\n"); 72 return 0; 73 } 74 //printf("%d\n",p); 75 dis[p]=0; 76 dfs(p); 77 if (!ans) 78 { 79 printf("-1\n"); 80 } else 81 { 82 for (int i=1;i<=n;++i) mmin=min(mmin,dis[i]); 83 for (int i=1;i<=n;++i) printf("%d\n",dis[i]-mmin); 84 } 85 86 }
另外还可以使用并查集的方法,A是B的双亲就表示A的位置比B的高,对于每个节点保存当前到双亲节点的距离(为正值),这样在并查集的树中,根节点与一个孩子的孩子的孩子.....的孩子的距离就可以在路径压缩的过程中计算出来:
int find(int x) { if (mset[x]==-1) return x; int t=mset[x]; d[x]+=d[t]; //递推出与根节点的距离 return mset[x]=find(t); }
对于两个不同集合的合并,由于在找集合的过程中使用了find函数,所以相关节点一定直接和集合树的根节点连接。设现在要连接的节点是a、b,它们的根节点分别是roota和rootb, a与b的距离为c, a在b的上面。集合的合并是集合根节点之间的连接,所以需要计算出根节点之间的距离P,b到roota的距离应为d[a]+c, b到rootb的距离为d[b] ,假设rootb成为roota的孩子,着P+d[b]=d[a]+c => P=d[a]+c-d[b].若P为负,则roota应成为roota的孩子,距离为P的绝对值。
1 #include <stdio.h> 2 #include <string.h> 3 int mset[500]; 4 int d[500],n,p; 5 int find(int x) 6 { 7 if (mset[x]==-1) return x; 8 int t=mset[x]; 9 d[x]+=d[t]; 10 return mset[x]=find(t); 11 } 12 13 int main() 14 { 15 while (~scanf("%d%d",&n,&p)) 16 { 17 memset(mset,-1,sizeof mset); 18 memset(d,0,sizeof d); 19 bool ans=true; 20 for (int i=0;i<p && ans;++i) 21 { 22 int a,b,c; 23 scanf("%d%d%d",&a,&b,&c); 24 int xx=find(a); 25 int yy=find(b); 26 int p=d[a]+c-d[b]; 27 if (xx!=yy) 28 { 29 if (p>=0) 30 { 31 mset[yy]=xx;d[yy]=p; 32 } else 33 { 34 mset[xx]=yy;d[xx]=-p; 35 } 36 } else 37 { 38 if (d[a]+c!=d[b]) ans=false; 39 } 40 } 41 for (int i=1;i<=n;++i) find(i); 42 if (ans) 43 for (int i=1;i<=n;++i) printf("%d\n",d[i]); 44 else printf("-1\n"); 45 } 46 }
这里能用并查集是利用了孩子和双亲关系的可传递性。