1. 程式人生 > 其它 >並查集 1.判斷成環否

並查集 1.判斷成環否

注意輸入!!

並查集

注意更改合併根節點數量 要先更改數量 然後再合併結點
tle 請看看自己查父函式有沒有return
或者使用
int find(int x)//找到x的祖宗節點。
{
if(x!=p[x])
p[x]=find(p[x]);
return p[x];
}//這樣寫對查詢的某個點 進行了轉化p[x]=根節點
集合的數量 在根節點上儲存 所以合併之後更新根節點

帶權並查集 :只有一個樹根節點但會變 需要維護書中的距離

判斷是否成環 https://www.acwing.com/activity/content/problem/content/1579/

題目給的是一個個 不同的點的時候 問給出點之後能不能成環 成環需要 P[a]==p[b]

#include <iostream>
#include <cstring>
#include <algorithm>
using namespace std;
const int N = 40010;
int p[N];
int n,m;
int find(int x){
    if(p[x]!=x){
        p[x]=find(p[x]);
    }
    return p[x];
}
int get(int a,int b){
    return a*n+b;
}
int main()
{
    cin >> n>>m;
    for (int i = 0; i < n*n; i ++ ){
        p[i]=i;
    }
    string op;
    int res=0;
    for (int i = 1; i <= m; i ++ ){
        int x,y;cin>>x>>y>>op;
        x--,y--;
        int a=get(x,y);
        int b;
        if(op=="D"){
             b=get(x+1,y);
        }else{
             b=get(x,y+1);
        }
        int pa=find(a),pb=find(b);
        if(pa==pb){
            res=i;
            break;
        }
        p[pa]=pb;
    }
    if(!res) cout << "draw";
    else 
    cout << res;
    return 0;
}

連通塊+01揹包

int n, m, vol;
int v[N], w[N];
int p[N];
int f[N];

int find(int x)
{
    if (p[x] != x) p[x] = find(p[x]);
    return p[x];
}

int main()
{
    cin >> n >> m >> vol;

    for (int i = 1; i <= n; i ++ ) p[i] = i;
    for (int i = 1; i <= n; i ++ ) cin >> v[i] >> w[i];

    while (m -- )
    {
        int a, b;
        cin >> a >> b;
        int pa = find(a), pb = find(b);
        if (pa != pb)
        {
            v[pb] += v[pa];
            w[pb] += w[pa];
            p[pa] = pb;
        }
    }

    // 01揹包
    for (int i = 1; i <= n; i ++ )
        if (p[i] == i)
            for (int j = vol; j >= v[i]; j -- )
                f[j] = max(f[j], f[j - v[i]] + w[i]);

    cout << f[vol] << endl;

    return 0;
}


離散化+並查集 https://www.acwing.com/problem/content/239/

#include <iostream>
#include <cstring>
#include <algorithm>
using namespace std;
const int N = 2e6+10;
int n,m;
int p[N];
unordered_map<int,int>s;
struct que{ 
    int x,y,e;
    
}query[N];
int find(int x){
    if(x!=p[x]){
         p[x]=find(p[x]);
    }
    return p[x];
}
int get(int x){
    if(s.count(x)==0) s[x]=++n;
    return s[x];
}
int main()
{
    int t;cin>>t;
    ios::sync_with_stdio(false);
    cin.tie(0);
    while(t--){
        n=0;s.clear();
        cin >> m;
        for (int i = 0; i < m; i ++ ){
            int x,y,e;
            cin >> x>>y>>e;
            query[i]={get(x),get(y),e};
        }
        for (int i = 1; i <= n; i ++ ) p[i]=i;
        
        for (int i = 0; i < m; i ++ ){
            if(query[i].e==1){
                int pa=find(query[i].x),pb=find(query[i].y);
                p[pa]=pb;
                
            }
        }
        bool flag=false;
        for (int i = 0; i < m; i ++ ){
            if(query[i].e==0){
                int pa=find(query[i].x),pb=find(query[i].y);
                if(pa==pb){
                    flag=true;
                    break;
                    
                }
                
            }
        }
        
        if(flag) puts("NO");
        else puts("YES");
    }
    
    return 0;
}

帶邊權並查集 離散化+帶邊權

https://www.acwing.com/problem/content/241/
複雜的 麻煩的我吐了 。。。

const int N = 20010;

int n, m;
int p[N], d[N];
unordered_map<int, int> S;

int get(int x)
{
    if (S.count(x) == 0) S[x] = ++ n;
    return S[x];
}

int find(int x)
{
    if (p[x] != x)
    {
        int root = find(p[x]);//root變成了根節點 因為後面return 是找到根的時候了
        d[x] += d[p[x]];//先讓距離增加
        p[x] = root;//再設定父節點為根節點
    }
    return p[x];
}

int main()
{
    cin >> n >> m;
    n = 0;

    for (int i = 0; i < N; i ++ ) p[i] = i;

    int res = m;
    for (int i = 1; i <= m; i ++ )
    {
        int l, r;
        string type;
        cin >> l >> r >> type;
        a = get(l - 1), b = get(r);

        int t = 0;
        if (type == "odd") t = 1;//是1類

        int pa = find(a), pb = find(b);
        if (pa == pb)//在同一個集合裡面
        {
            if (((d[a] + d[b]) % 2 + 2) % 2 != t)//看看是否矛盾
            {
                res = i - 1;
                break;
            }
        }
        else//之前沒有這個l-1到r的關係,現在要結合在一起了
        {
            p[pa] = pb;
            d[pa] = d[a] ^ d[b] ^ t;//兩類集合之間的那個距離? 若da^?^db=1類  ?=1^da&db
        }
    }

    cout << res << endl;

    return 0;
}

網路分析https://www.acwing.com/activity/content/code/content/603227/

題意:一開始的點都為獨立的點 完成連線之後的獨立的點在連線之前加上的值 單獨算
難點 :怎麼單獨獨立算 ( 連線之前讓另一個連線節點減去本身的值 那麼其下面的節點往上找就不會加錯 或 加一個空節點) 不是根節點=d[i]+d[find(i)]

d[x]陣列連線的時候怎麼處理:細節

#include <iostream>
#include <cstring>
#include <algorithm>

using namespace std;

const int N = 10010;

int n, m;
int p[N], d[N];


int find(int x)
{
    if (p[x] == x || p[find(p[x])] == p[x]) return p[x];//x是根點點 或者 x父節點是根節點 都可以通過返回px表示根節點
    int r = find(p[x]);
    d[x] += d[p[x]];
    p[x] = r;
    return r;
}

int main()
{
    scanf("%d%d", &n, &m);
    for (int i = 1; i <= n; i ++ ) p[i] = i;
    while (m -- )
    {
        int t, a, b;
        scanf("%d%d%d", &t, &a, &b);
        if (t == 1)
        {
            a = find(a), b = find(b);
            if (a != b)//原來不是一個根節點的時候才這麼處理
            {
                d[a] -= d[b];//關鍵連線之前先減去這個值
                p[a] = b;
            }
        }
        else
        {
            a = find(a);
            d[a] += b;
        }
    }

    for (int i = 1; i <= n; i ++ )
        if (i == find(i)) printf("%d ", d[i]);//這一步必須要find(i);//如果1->2->3 但是第一步合併2,4 然後又在4上面價值 那麼變成1->2->4 3->4 1不是直接和4相連所以需要find一下 d[i]+d[find(i)]是建立在i直接連的根節點基礎上
        else printf("%d ", d[i] + d[find(i)]);
    puts("");

    return 0;
}


修改陣列https://www.acwing.com/problem/content/1244/

![]
(https://img2022.cnblogs.com/blog/2525875/202204/2525875-20220405231857775-32125794.png)

p[x]裡面的根節點 x指向下一個應該填的數

#include <iostream>
#include <cstring>
#include <algorithm>
using namespace std;
const int N = 2e6;
int p[N];
int find(int x){
    if(x!=p[x]) p[x]=find(p[x]);
    return p[x];
}
int main()
{
    int n;cin>>n;
    for (int i = 1; i < N; i ++ ){
        p[i]=i;    
    }
    for (int i = 0; i < n; i ++ ){
        int x;cin>>x;
        x=find(p[x]);
        p[x]=x+1;
        cout << x<<" ";
    }
    return 0;
}

題目好朋友


const int n=110;
int father[N];//用於查詢,下標是某個節點,值是存放父親結點
int isRoot[N];//存放每個集合的代表結點
int findfather(int x)//尋找父親的函式
{  //int a=x;//為了路徑壓縮
    while(x!=father[x]){
    x=father[x];
}

//while(a!=father[a]){  //路徑壓縮
//    int z=a;
//    a=father[a];
//    father[z]=a;
//}

return x

void union(int a,int b){
  int faA=findfather(a);//表示根節點
  int faB=findfather(b);//表示根結點
  if(faA!=faB){
  father[faA]=faB;//將a最上面結點的父親從自己設為b最上面的父親
  }
}
void init(int n){
  for(int i=1;i<n;i++){
      father[i]=i;
  isRoot[i]=false;//該結點的代表  
  }
}

}
int n,m,a,b;
cin<<n<<m;
init(n);//n表示總共有的人進行初始話
for(int i=0;i<m;i++){//m表示為所在的集合
    cin<<a<<b;
    union(a,b);
}
for(int i=1;i<=n;i++){//只有跟結點才設為真
  isroot[findfather(i)]=true;
}
int ans=0;
for(int i=1;i<=n;i++){//統計
  ans+=isroot[i];
  
}
cout<<ans;
return 0;



計算連通塊

int calblock(int n){//傳入一個值
    int block=0;
    for(int i=1;i<=n;i++){
    	
        root[findfathher(i)]=true;//讓根節點變為1
        
    }
    for(int i=1;i<=n;i++){
        block+=root[i];//然後加上這個就行了
    }
    return block;
}
#include <bits/stdc++.h>
using namespace std;

int father[5005];
int isroot[5005];

int findfather(int x){//尋父親函式
// int a=x;
 while(x!=father[x])
 x=father[x];
//while(a!=father[a]){
//	int z=a;
//	a=father[a];
//	father[z]=x;
}

void Union(int a,int b){//聯合函式
	int faA=findfather(a);
	int faB=findfather(b);
	if(faA!=faB){
		father[faA]=faB;
	}
}
void init(int n){//初始話函式
	for(int i=1;i<=n;i++){
		father[i]=i;
		isroot[i]=false;
	}
}

int main()
{
	int n,m,p;
	cin>>n;
	init(n);//初始話
	int a,b;
	cin>>m; 	cin>>p;
	for(int i=1;i<=m;i++){
		cin>>a>>b;
		Union(a,b);//聯合
	}	
	for(int i=1;i<=p;i++){//分析每一對
		cin>>a>>b;
		if(findfather(a)==findfather(b))
		cout<<"Yes"<<endl;
		else cout<<"No"<<endl;	
	}	
	
	return 0;
}

洛谷:修復公路,連成幾個的問題,都是用n--,當n==某值的時候輸出

#include <bits/stdc++.h>
using namespace std;

int father[5005];
int isroot[5005];
struct way{
	int x,y,z;
}a[100005];//設計到結構體的排序問題

int findfather(int x)
{	int a=x;
	while(x!=father[x])
	x=father[x]; 
	while(a!=father[a]){
		int z=a;
		a=father[a];
		father[z]=x;		
	} 

}
void Union(int x,int y){
	
	int faX=findfather(x);
	int faY=findfather(y);
	if(faX!=faY)
	father[faX]=faY;
	
}
void init(int n){
	for(int i=1;i<=n;i++){
		isroot[i]=false;
		father[i]=i;
	} 
}
bool cmp(way a ,way b){
	return a.z<b.z;
}
int main()
{
	int n,m,time=0;
	cin>>n>>m;
	init(n);
	int z,x,y;
	for(int i=1;i<=m;i++){
		cin>>a[i].x>>a[i].y>>a[i].z;
		
	}
	sort(a ,a+m+1 , cmp);
//	for(int i=1;i<=m;i++) cout<<a[i].z<<" ";
	for(int i=1;i<=m;i++){
				if(findfather(a[i].x)!=findfather(a[i].y)){
		Union(a[i].x,a[i].y);//只要不是同一個集合就聯合起來
		n--;	
		}

		if(n==1) {//發現只剩一個村莊的時候,說明已經成為了一個整體
			cout<<a[i].z;//因為修路可以同時進行
			return 0;
		}
	}
	cout<<"-1";
	
	return 0;
}

通訊系統http://codeup.hustoj.com/problem.php?cid=100000615&pid=0

有小坑:額外附加了一個條件,單個端點只接收一次訊息,所以,不能有環出現,
只有不是環的時候才能輸出yes
怎麼判斷?對圖進行樹化,只能根據樹的邊數為n-1定則,而且要所有端點必須為同一集合,那麼M必須等於N-1

找個數 輸出王先生可以保留的最大男孩數量。(即有多少個人,在房間裡的人都是朋友在房間裡)

#include <cstdio>
#include <vector>
#include <utility>
using namespace std;
const int maxn=10000010;
int father[maxn]={0},num[maxn];
int maxnum;
 
int findFather(int a)
{
	int x=a;
	while(x!=father[x])
	{
		x=father[x];
	}
	while(a!=father[a])
	{
		int z=a;
		a=father[a];
		father[z]=x;
	}
	return x;
}
void Union(int a, int b)
{
	int A=findFather(a);
	int B=findFather(b);
	if(A!=B)//不是一個集合的話 有變動 注意
	{
		father[A]=B;//選擇將一個根結點的父親變為另一根結點

		num[B]+=num[A];//讓另一個根結點的數值增加,
		if(maxnum<num[B]) 並且更新最大值
			maxnum=num[B];
	}(要選出每個集合中個數的最大值)
}
void init(int n)
{
	father[n]=n;
	num[n]=1;
}
int main()
{
	int n,dot1,dot2;
	vector<pair<int,int> > input;
    while(~scanf("%d",&n))
    {
    	if(n==0)
    	{
    		printf("1\n");
    		continue;
		}
    	input.clear();
    	for(int i=0;i<n;++i)
    	{
    		scanf("%d %d",&dot1,&dot2);//輸入每對的資訊
			init(dot1);//一個個人初始化節省空間?
			init(dot2);
			input.push_back(make_pair(dot1,dot2));
		}
		maxnum=0;
		for(int i=0;i<input.size();++i)
		{
			Union(input[i].first,input[i].second);//聯合
		}
		printf("%d\n",maxnum);
	}
    return 0;

查詢圖的聯通塊

#include<bits/stdc++.h>
using namespace std;
const int N=1005;
vector<int>graph[N];
bool vis[N];
int isfather[N];
int isroot[N];
int findfather(int i){
    int x=i;
    while(x!=isfather[x]){
        x=isfather[x];//父節點=等於祖宗結點
    }
    while(i!=isfather[i]){
        int n=i;
        i=isfather[i];
        isfather[n]=x;
        
        
    }return x;
}
void combine(int x,int y){
    int faa=findfather(x);
    int fab=findfather(y);
    if(faa!=fab){
        isfather[faa]=fab;
    }
}
void init(){
    for(int i=0;i<N;i++){
        isfather[i]=i;
        vis[i]=false;
    }
}//查父合併初始話

int main(){
    int n,m,k;
    cin>>n>>m>>k;
    for(int i=0;i<m;i++){
        int x,y;
        cin>>x>>y;
        graph[x].push_back(y);
        graph[y].push_back(x);
           
    }
    int currentpoint;
    for(int i=0;i<k;i++){
        cin>>currentpoint;
        init();
        for(int i=0;i<=n;i++){//遍歷每個頂點,列舉每條邊,合併每個邊
            for(int j=0;j<graph[i].size();j++){
                if(i==currentpoint||graph[i][j]==currentpoint) continue;
                combine(i,graph[i][j]);
                
            }
        }
        int block=0;
        for(int i=1;i<=n;i++){//你仍然需要遍歷所有的頂點
            if(i==currentpoint) continue;
            int fa_i=findfather(i);//對於這個根節點如果沒有遍歷過就說明是另外一個塊
            if(vis[fa_i]==false){
                block++;
                vis[fa_i]=true;
            }
        }
        if(block>0)
        cout<<block-1 <<endl;
        else
            cout<<block <<endl;
    }
    return 0;
}

字串歸類 https://www.acwing.com/problem/content/4307/

字串 只要由相同的字母就可以歸為一類 所有字串被劃分為多少類

#include <iostream>
#include <cstring>
#include <algorithm>
using namespace std;
const int N = 2e5+10;
int p[N],n;
int id[26];
char str[55];

int find(int x){
    if(x!=p[x]) p[x]=find(p[x]);
    return p[x];
}


int main()
{
    cin>>n;
    for(int i=1;i<=n;i++) p[i]=i;int res=0;
    for (int i = 1; i <= n; i ++ ){
        cin>>str;
        for (int j = 0; str[j]; j ++ ){
            int x=str[j]-'a';
            if(id[x]){
                p[find(id[x])]=i;//如果這個字母出現過 就把這個字母的之前出現的字串合併到當前出現的字串
                
            }
            
            else id[x]=i;
        }
        
    }
    for(int i=1;i<=n;i++) if(p[i]==i) res++;;
    cout << res;
}



帶權並查集 食物鏈

#include <iostream>

using namespace std;

const int N = 50010;

int n, m;
int p[N], d[N];
  1 ->  2 ->  3->  跟
  1 ->  2 -> 跟
 d[x]      d [p [x] ]
  1->  跟
int find(int x)
{
    if (p[x] != x)//不是根節點  
    {
        int t = find(p[x]);//存放根節點 
        d[x] += d[p[x]];//d[px]存放的是到根節點的距離,因為上一步遞迴把p[x]變成了根節點
        p[x] = t;//把
    }
    return p[x];
}

int main()
{
    scanf("%d%d", &n, &m);

    for (int i = 1; i <= n; i ++ ) p[i] = i;

    int res = 0;
    while (m -- )
    {
        int t, x, y;
        scanf("%d%d%d", &t, &x, &y);

        if (x > n || y > n) res ++ ;
        else
        {
            int px = find(x), py = find(y);//px表示x的根節點
            if (t == 1)
            {
                if (px == py && (d[x] - d[y]) % 3) res ++ ;//如果現在的兩個結點已經連線到一起了 計算他們的距離 如果是同類 那麼到3的距離餘的距離應該相等位0 這裡為1說明不成立是假話 
                else if (px != py)//如果沒有連線到一起而且必是真話 需要連線到一起
                {
                    p[px] = py;//把py便成根節點
                    d[px] = d[y] - d[x];//如果同類 那麼 d[x]+d[p[x]] == d[y] 在模3的意義下相等 所以反推出d[x]
 
                }
            }
            else//設x能吃y 那麼x比y少1 1吃0
            {
                if (px == py && (d[x] - d[y] - 1) % 3) res ++ ;//如果x吃y 那麼x和 y+1在模3 的意義下相等 這裡餘數不為0 數說明是假話
                else if (px != py)//必然是真話
                {
                    p[px] = py;
                    d[px] = d[y] + 1 - d[x];//如果x吃y  d[x]+d[px] == d[y]-1 在3的意義下 
                }
            }
        }
    }

    printf("%d\n", res);

    return 0;
}