VIJOS P1540 月亮之眼

时间:2023-03-09 19:33:31
VIJOS P1540 月亮之眼

【题目大意】

有多个珠子,给出部分珠子之间的相对上下位置和间距,问你这些珠子在满足给出的条件下,是否能把珠子排列在一条竖直直线上,如果能,求出每个珠子距离最高的珠子的距离,珠子的位置可重叠。

【分析】

可以根据珠子的位置关系建立一张有向图,A->B 为A比B高,权值为之间的距离。可以发现必须满足下列三种情况:

1、图有连通;无法比较出不同连通分支的上下关系。
               2、有向图没有环;根据位置的传递关系,不可能自己比自己低。
               3、如果从A到B有多条路径,路径的长度都应该一样;要不然B的位置关系就会有二义性。

我本来的想法是按顺序验证上面三条规则,把有向图转为无向图判联通,用拓扑排序判环,用DFS来判路径长度,想想太复杂了。后来想想其实没必要那么复杂。对于每条有向边,添加一条负权反向边,任意选定一个点,假设它是最高点,离最高点的距离是0,用DFS搜索可连向的点,如果新点未访问,则新点的距离就是当前点的距离加上边的权值,如果新点被访问过,这需要验证当前点的距离加上边权是否与新点原来的权值相等,如果不相等,则说明有冲突。如果DFS结束还有点没被访问,则说明图不连通。如果发现有点的距离为负数,则说明那个点比搜索开始的点的距离更高。

DFS的代码很简单:

 #include <stdio.h>
#include <string.h>
struct node
{
int x,y,c,next;
void mset(int a,int b,int z,int nn)
{
x=a;
y=b;c=z;
next=nn;
}
}ev[];
int ej,n,p;
int map[];
int vv[][];
int num[];
int dis[];
bool ans;
int mmin;
int min(int a,int b){return a<b?a:b;}
void insert(int x,int y,int v)
{
int tem=map[x];
ev[ej++].mset(x,y,v,tem);
map[x]=ej-;
vv[x][y]=v;
vv[y][x]=-v;
}
void dfs(int x)
{
if (num[x]) return;
num[x]=;
int p=map[x];
while (p!=- && ans)
{
node tt=ev[p];
int t2=dis[x]+tt.c;
if (dis[tt.y]!=- && dis[tt.y]!=t2)
{
ans=false;
return;
}
dis[tt.y]=t2;
dfs(tt.y);
p=tt.next;
}
}
int main()
{
ej=;mmin=;
ans=true;
//freopen("in.txt","r",stdin);
memset(map,-,sizeof map);
memset(num,,sizeof num);
memset(dis,-,sizeof dis);
scanf("%d%d",&n,&p);
for (int i=;i<p;++i)
{
int a,b,c;
scanf("%d%d%d",&a,&b,&c);
insert(a,b,c);
insert(b,a,-c);
num[b]=;
}
int p=;
for (int i=;i<=n;++i)
if (num[i]==) {p=i;break;}
memset(num,,sizeof num);
if (p==)
{
printf("-1\n");
return ;
}
//printf("%d\n",p);
dis[p]=;
dfs(p);
if (!ans)
{
printf("-1\n");
} else
{
for (int i=;i<=n;++i) mmin=min(mmin,dis[i]);
for (int i=;i<=n;++i) printf("%d\n",dis[i]-mmin);
} }

另外还可以使用并查集的方法,A是B的双亲就表示A的位置比B的高,对于每个节点保存当前到双亲节点的距离(为正值),这样在并查集的树中,根节点与一个孩子的孩子的孩子.....的孩子的距离就可以在路径压缩的过程中计算出来:

int find(int x)
{
if (mset[x]==-) 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的绝对值。

 #include <stdio.h>
#include <string.h>
int mset[];
int d[],n,p;
int find(int x)
{
if (mset[x]==-) return x;
int t=mset[x];
d[x]+=d[t];
return mset[x]=find(t);
} int main()
{
while (~scanf("%d%d",&n,&p))
{
memset(mset,-,sizeof mset);
memset(d,,sizeof d);
bool ans=true;
for (int i=;i<p && ans;++i)
{
int a,b,c;
scanf("%d%d%d",&a,&b,&c);
int xx=find(a);
int yy=find(b);
int p=d[a]+c-d[b];
if (xx!=yy)
{
if (p>=)
{
mset[yy]=xx;d[yy]=p;
} else
{
mset[xx]=yy;d[xx]=-p;
}
} else
{
if (d[a]+c!=d[b]) ans=false;
}
}
for (int i=;i<=n;++i) find(i);
if (ans)
for (int i=;i<=n;++i) printf("%d\n",d[i]);
else printf("-1\n");
}
}

这里能用并查集是利用了孩子和双亲关系的可传递性。