1. 程式人生 > >【並查集】並查集

【並查集】並查集

進行 += solution ref 利用 ini html 是否 取值

模板

數組版:

int parent[MAX_N];
int rank[MAX_N];

void Init(int n){
	for(int i = 0; i < n; ++i){
		parent[i] = i;
		rank[i] = 0;
	}
}

int Find(int x){
	if(parent[x] = x){
		return x;
	} else {
		return Find(parent[x]);
	}
}

void Union(int x, int y){
	x = Find(x);
	y = Find(y);
	if(x == y)
		return;
	if(rank[x] < rank[y]){
		parent[x] = y;
	} else {
		parent[y] = x;
		if(rank[x] == rank[y]) rank[x]++;
	}
}

結構體版:

struct Node
{
    int rank;
    int parent;
 } node[MAX_N];

void Init(int n){
	for(int i = 0; i < n; ++i){
		node[i].parent = i;
		node[i].rank = 0;
	}
}

int Find(int x){
	if(node[x].parent = x){
		return x;
	} else {
		return Find(node[x].parent);
	}
}

void Union(int x, int y){
	x = Find(x);
	y = Find(y);
	if(x == y)
		return;
	if(node[x].rank < node[y].rank){
		node[x].parent = y;
	} else {
		node[y].parent = x;
		if(node[x].rank == node[y].rank) node[x].rank++;
	}
}

例題

例1 poj 1182(帶權並查集)

解題思路:

  這個題是非常經典的並查集問題。並查集作用:查詢a和b是否屬於同一組;合並a和b所在的組。註意並查集無法進行分割操作。利用題目中動物之間的相對關系可以建立一個並查集。對每一個元素,把它對父節點的關系用數組rank[i]表示,即relation,作為權值。0:與父節點同類 1:吃父節點 2:被父節點吃。

  路徑壓縮。為了使查找效率提高,我們需要使樹的高度盡可能小,讓它只有一層,即在查找的過程中,把樹中一條路徑上的所有點都連在根節點上,從而加快了查找速度,與此同時,還要更新與此節點有關的其他變量,比如此節點與父節點的權值(父節點變為根節點),該樹的高度等等。

對於此題來講,就是要更新節點與父節點的關系,因為此節點的父節點已經變為根節點。那麽這裏如何推導出此節點與根節點的關系呢。假設此節點為x,其父節點為y,y的父節點為z。則:

               rank[x]   rank[y]    x與z的關系權值
		          0        0         0=(i + j)%3  
		          0        1         1=(i + j)%3  
		          0        2         2=(i + j)%3
		          1        0         1=(i + j)%3 
                  1        1         2=(i + j)%3 
                  1        2         0=(i + j)%3 
                  2        0         2=(i + j)%3 
                  2        1         0=(i + j)%3 
                  2        2         1=(i + j)%3

  

  推導公式:rank[x] = (rank[x] + rank[y]) % 3; 即對於i節點,更新公式為:rank[i] = (rank[i] + rank[parent[i]]) % 3。不過還有更簡便的方法:模擬向量的運算x->z = x->y + y->z,所以rank[x] = (rank[x] + rank[y])% 3。對3取模是為了保證結果為0,1,2。

  最後是集合的合並操作。合並操作並不復雜,復雜的是更新集合間的關系(即集合根節點的關系)。這裏有大神使用向量的形式來計算更新關系權值,假設需要合並x和y,查詢得知x和y的根節點分別為:x_p,y_p,如果將x_p連接到y_p,那麽rank[x_p]即為x_p與根節點y_p的關系。x_p->y_p = x_p->x + x->y + y->y_p = (-x->x_p + x->y + y->y_p)。所以更新公式為rank[x_p] = (-rank[x] + type - 1 + rank[y] + 3) % 3。(加上3是為了防止出現負數的情況;對3取模是為了保證結果的取值範圍)。type即為輸入的num。type為1,x與y同類,type為2,x吃y。

Solution:from 專註如一

#include <cstdio.h>
#include <cstring>
#include <algorithm>
using namespace std;
const int MAX_N = 50000 + 10;
int parent[MAX_N], rank[MAX_N];

void Init(int n){
    for(int i = 0; i < n; ++i){
        parent[i] = i;
        rank[i] = 0;
    }
}

int Find(int x){
    if(parent[x] == x){
        return x;
    } 
    int y = Find(parent[x]);
    rank[x] = (ran[x] + ran[parent[x]]) % 3;
    return parent[x] = y;
}

int Union(int x, int y, int type){
    x_p = Find(x);
    y_p = Find(y);
    if(x_p == y_p){
        if((rank[x] - rank[y] + 3) % 3 == type - 1)
            return 0;
        return 1;
    }
    parent[x_p] = y_p;
    rank[x_p] = (-rank[x] + type - 1 + rank[y] + 3) % 3;
    return 0;
}

int main(){
    int n, k, res = 0;
    int type, x, y;
    scanf("%d %d", &n, &k);
    Init(n);
    for(int i = 0; i < k; ++i){
        scanf("%d %d %d", &type, &x, &y);
        if(x == y && type == 2)
            ++res;
        else if(x > n || y > n)
            ++res;
        else
            res += Union(type, x, y);
    }
    printf("%d\n", res);
    return 0;
}

【並查集】並查集