普通并查集. 合并 均摊O(log(n)) 查询 均摊O(log(n))
1 //常用版本 2 3 //Union Find 4 int f[1005000]; 5 6 void INIT(int size) 7 { for(int i=0;i<=size;i++) f[i]=i; } 8 9 int findf(int x) 10 { return f[x]==x ? x : f[x]=findf(f[x]); };11 12 int connect(int x)13 { f[findf(a)]=findf(b); }
VIJOS 1443 会卡掉不加启发式的并查集.
优化并查集. 加上了启发式合并. 复杂度: 合并 均摊O(α(n)); 查询 均摊O(α(n)). α函数是Ackerman函数的反函数.一般认为其小于等于4.
1 //union find 2 int f[5000],r[5000]; 3 inline void INIT(int size) 4 { for(int i=0;i r[b]) swap(a,b);12 int fa=findf(a);13 int fb=findf(b);14 r[fb]+=r[fa];15 f[fa]=fb;16 }
使用r数组表示"秩",即集合大小,存储在每个集合的根节点.
合并的时候将秩小的集合的根节点的父亲设到秩大的集合的根.
常数较上者而言更加大.
另外,如果离线处理询问,那么可以通过建图然后FloodFill的方式达到严格O(n)预处理+O(1)询问,常数较大.
可拆分并查集.
最坏情况: 合并 O(n) 查询 O(n) 拆分 O(n)
平均情况(对于随机生成的数据): 合并 O(logn) 查询 O(logn) 拆分 O(logn)
对于某种方式生成的随机数据跑得很快的样子?
反正随便卡.....
1 //union find 2 int f[1050]; 3 void INIT(int size) 4 { for(int i=0;i<=size;i++) f[i]=i; } 5 6 int findf(int x) 7 { while(f[x]!=x) x=f[x]; return x; } 8 9 void setroot(int x)10 { if(f[x]!=x) setroot(f[x]); f[x]=f[f[x]]=x; }11 12 void link(int a,int b)13 { setroot(a); f[a]=b; }14 15 void cut(int a,int b)16 { setroot(a); f[b]=b; }
可以看到后边这种比较像LCT...
链式并查集.
复杂度 均摊 O(nlogn)
AC VIJOS 1443 银河英雄传说
常数好大....复杂度也好大.......这道题用了1653ms.....
这个并查可以用于维护一些奇怪的东西..... (做这题的时候脑子里全是LCT怎么办)
1 //Union Find 2 int L[30050]; //left node pointer. 3 int R[30050]; //right node pointer. 4 int C[30050]; //center node. 5 int tot[30050]; //chain nodes amount. 6 int nxt[30050]; //next node. 7 int v[30050]; //value of all nodes. 8 9 void INIT(int size)10 {11 for(int i=0;i<=size;i++)12 L[i]=R[i]=C[i]=i,tot[i]=1,nxt[i]=-1;13 }14 15 void Merge(int x,int y)16 {17 //link x to y's tail.18 int cx=C[x];19 int cy=C[y];20 if(tot[cx]>tot[cy]) //change chain y's info21 {22 for(int i=L[cy];i!=-1;i=nxt[i])23 C[i]=cx; //chagne center node.24 nxt[R[cy]]=L[cx];25 L[cx]=L[cy];26 tot[cx]+=tot[cy];27 }28 else //change chain x's info.29 {30 for(int i=L[cx];i!=-1;i=nxt[i])31 C[i]=cy; //change center node.32 nxt[R[cy]]=L[cx];33 R[cy]=R[cx];34 tot[cy]+=tot[cx];35 }36 }37 38 39 char op[505000];40 int a[505000];41 int b[505000];42 43 int n=30000,m;44 45 int main()46 {47 m=getint();48 49 INIT(n);50 51 for(int i=0;i
View Code 做法就是对每一条链维护一个"中心节点",这个节点存储了链的总结点数,链的左端点和链的右端点.
合并的时候把这些存储的值的转移安排好就行了.......
实在不懂就看代码......
千万注意Merge函数中nxt数组赋值的顺序......
next数组的值一定要在cx赋值完毕以后修改,不然赋值操作会把一整个集合扫一遍!....
这样总时间复杂度就是O(n^2)的了....害我T了一发 TAT
AC BZOJ 1483 又是按秩合并.....
全都是调试用的注释的版本
1 #include 2 #include 3 #include 4 5 #include 6 #include 7 #include 8 #include 9 10 #include 11 #include 12 #include
View Code
不带注释的版本
1 #include 2 #include 3 #include 4 5 #include 6 #include 7 #include 8 #include 9 10 #include 11 #include 12 #include
View Code
按秩合并呢,总结起来两点:
1.如何存储集合的大小; 2.如何合并两条链.
存储集合的大小,可以直接对每条链维护一个中央节点来存储,然后合并的时候注意一下把数据合并过去.
也可以用一种集合的对应关系(在本题中是颜色)来快速存储与合并.
合并两条链,先把a(我们把较小的那条链叫做a)的全局数据(比如链长)合并到b(另外一条链).
然后扫描a,每扫描到一个点就把它的数据(比如中央节点指针)改掉.
链a的末尾节点要特别处理,把链a末尾接到链b上(或把链b末尾接到链a上).
然后把b的链头改成a的链头,再把a的链头置空(或把a的链头改成b的链头,两个链头都不置空).
链头是否置空以及是否能够左右两次合并要依照题目来看.
....