实际上很早之前林荫是有这个技能的。(废话!要不直接叫小学习好了)
众所周知,并查集可以用来维护一些元素之间相连的关系(不知道的出门右转幼儿园)
而状态压缩可以使得并查集查询一对元素的关系的速度变快(O1)
状态压缩之后的并查集实际上是一个由fa数组(相当于单向链表)构成的菊花图
那么,如何用并查集来维护元素之间实际的数量关系呢?
先看一个例子:
假定现在有3个小朋友ABC,B比A大3岁,C比B大2岁。对于这个问题,如果用不带状压的并查集进行表示,那么就可以得到一条链。B到A的边权3表示
B比A大3岁,C到B的边权2表示C比B大2岁。好的我们现在询问C和A的年龄关系。如果一个一个查询这条链上的点那么一定很慢。
是否可以考虑一下,让ABC三者的年龄都与某个人的年龄相关呢(实际上这时就是尝试转化为菊花图)
我们可以考虑让C直接与A相连,但是边权怎么办?
实际上C和A的年龄差就是C和B的差加上B和A的差。那么我们就让菊花图中A——C的边权为dis[B][A]+dis[C][B]
这样的话,我们就可以做到O1查询两个点之间的数量关系了(就比如询问CB年龄差实际上就是CA减去BA)
那么实际上代码也不难实现:
int Find(int x) { if(x==fa[x]) return x; int t=Find(fa[x]); dis[x]+=dis[fa[x]]; fa[x]=t; return t; }
如果找到最终父亲(就代表这个点是和菊花图的花心相连不用改)直接返回
否则和自己的权就和自己的父亲相加(因为这个时候自己的父亲已经链接到花心上了,权也是父亲和花心的关系)求出自己和花心的关系
至于t为啥一直都是菊花图花心(最终父亲),因为t一直都是Find(fa[x])的结果,fa[x]在对x进行修改时,要么本身就是花心,要么已经和花心直接相连。
下面来两道练手水题:
T1:洛谷P1196NOI2002银河英雄传说
#include<iostream> #include<cstdio> using namespace std; int dis[30001];//每艘战舰到所在行的旗舰中间所隔战舰数 int len[30001];//每艘战舰所在行的战舰总数 int fa[30001]; int Find(int x) { if(x==fa[x]) return x; int t=Find(fa[x]); //先一步步跟到最终父节点 dis[x]+=dis[fa[x]];//假定fa[fa[x]]为最终父节点,那么dis[fa[x]]就相当于直接连接到菊花图花心的边权,dis[x]+=dis[fa[x]]相当于将x从fa[x]直接链接到fa[fa[x]] fa[x]=t;//这时再把x直接链接到菊花图的花心 return fa[x]; } void Move(int x,int y) { int fx=Find(x); int fy=Find(y); dis[fx]+=len[fy]; len[fy]+=len[fx]; fa[fx]=fy; } int ABS(int x) { return max(x,-x); } char x[1]; int a1,a2; int LINYIN() { int T; scanf("%d",&T); for(int i=1;i<=30000;i++) { fa[i]=i; len[i]=1; dis[i]=0; } while(T--) { scanf("%s%d%d",x,&a1,&a2); if(x[0]=='M') { Move(a1,a2); } else { if(Find(a1)!=Find(a2)) { printf("%d ",-1); } else { printf("%d ",ABS(dis[a1]-dis[a2])-1); } } } return 0; } int LWH=LINYIN(); int main() { ; }
T2:洛谷P5092方块游戏
#include<iostream> #include<cstdio> using namespace std; int len[30001]; int dis[30001]; int fa[30001]; int Find(int x) { if(x==fa[x]) return x; int t=Find(fa[x]); dis[x]+=dis[fa[x]]; fa[x]=t; return t; } void Move(int x,int y) { int fx=Find(x); int fy=Find(y); fa[fy]=fx; dis[fy]=len[fx]; len[fx]+=len[fy]; } int n,a1,a2; char k[1]; int LINYIN() { scanf("%d",&n); for(int i=1;i<=30000;i++) { dis[i]=0; fa[i]=i; len[i]=1; } for(int i=1;i<=n;i++) { scanf("%s",k); if(k[0]=='M') { scanf("%d%d",&a1,&a2); Move(a1,a2); } else { scanf("%d",&a1); int fx=Find(a1); printf("%d ",len[fx]-(dis[a1]+1)); //cout<<len[fx]-(dis[a1]+1)<<endl; } } return 0; } int LWH=LINYIN(); int main() { ; }
完结撒花!!!