其他分享
首页 > 其他分享> > 并查集 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://www.icode9.com/i/l/?n=22&i=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;
}

标签:判断,return,int,查集,father,节点,成环否,include,find
来源: https://www.cnblogs.com/liang302/p/15713015.html