Never Wait for Weights
这一个题目用并查集+边权来解决, 其间自然用到了路径压缩。
(起初并没有用边权来做,然后在合并子集更新的时候发现超时了)
首先初始化节点信息,每个节点的父亲都定义成自己,每个边权(定义成父亲减去儿子后剩下的值)此时当然是0
然后读入数据,当读入到!号时,找到所输入两个元素的父节点,然后将两个元素合并,并更新作为儿子子节点的权值。
查询的时候如果两个元素的父节点不相同则说明两个元素不在同一个集合内,输出UNKNOWN 如果再同一个集合内的话,直接边权向减就行了。
(因为用到了路径压缩,所以在查询结束之后每个子节点的直接父亲就是根节点,边权就是与根节点的差于是乎
edge[a] = root - a; edge[b]=root - b; 则 b-a =root - edge[b] -(root - edge[a]) =edge[a] - edge[b] )
时间复杂度:集合的合并算法很简单,只要将两棵树的根结点相连即可,这步操作只要O(1)时间复杂度
采用路径压缩之后,查找的时间复杂度也降低为了O(1) m 次操作 !的合并 ?的话查找两次 合并 那就O(m)吧
空间复杂度 O(n)
#include<cstdio>
#include<cstring>
#define MAX 100010
int father[MAX],edge[MAX];
int n,m,i,a,b,c;
char s[5];
void unio(int fa,int fb,int c)
{
father[fa]= fb;
edge[fa] +=c;
}
int findFather(int i)
{
if(father[i]==i) return father[i];
else
{
int ans= findFather(father[i]);
edge[i]+=edge[father[i]];
return father[i]=ans;
}
}
int main()
{
//freopen("1.txt","r",stdin);
while(scanf("%d%d",&n,&m)==2)
{
if(n==0&&m==0) break;
for(i = 1;i<=n;i++)
{
father[i]= i;
edge[i] = 0;
}
while(m--)
{
scanf("%s",s);
if(s[0]=='!')
{
scanf("%d%d%d",&a,&b,&c);
int fa = findFather(a);
int fb = findFather(b);
c = c + edge[b]-edge[a];
if(fa==fb) continue;
else
{
unio(fa,fb,c);
}
}
else if(s[0]=='?')
{
scanf("%d%d",&a,&b);
int fa = findFather(a);
int fb = findFather(b);
if(fa!=fb)
{
printf("UNKNOWN\n");
}
else printf("%d\n",edge[a] - edge[b]);
}
}
}
return 0;
}