zoukankan      html  css  js  c++  java
  • 并查集的删除操作

    UVA11987--Almost Union-Find

    zz:https://blog.csdn.net/scut_pein/article/details/8660719?depth_1-utm_source=distribute.pc_relevant.none-task&utm_source=distribute.pc_relevant.none-task

    I hope you know the beautiful Union-Find structure. In this problem, you're to implement something similar, but not identical.

    The data structure you need to write is also a collection of disjoint sets, supporting 3 operations:


    1 p q : Union the sets containing p and q. If p and q are already in the same set, ignore this command.


    2 p q : Move p to the set containing q. If p and q are already in the same set, ignore this command


    3 p : Return the number of elements and the sum of elements in the set containing p.


    Initially, the collection contains n sets: {1}, {2}, {3}, ..., {n}.


    Input Description
    There are several test cases. Each test case begins with a line containing two integers n and m (1<=n,m<=100,000), the number of integers, and the number of commands. Each of the next m lines contains a command. For every operation, 1<=p,q<=n. The input is terminated by end-of-file (EOF). The size of input file does not exceed 5MB.
    Output Description
    For each type-3 command, output 2 integers: the number of elements and the sum of elements.
    题目大意:有n个集合,提供三种操作
    1 p q:将p所在的集合和q所在的集合并起来
    2 p q:将p元素移到集合q所在的集合
    3 p:求出p所在集合有多少个元素并输出这些元素的和


    Sample Input

    5 7
    1 1 2  //集合1与2合并(集合操作)
    2 3 4  //元素3放到元素4所在的集合中
    1 3 5  //将[3,4]与[5]合并,得到[3,4,5]
    3 4   //查询4所在的集合
    2 4 1 //将元素4移动到1所在集合,得到[1,2,4]
    3 4
    3 3  //查询3所在的集合[3,5]

    Sample Output

    3 12
    3 7
    2 8

    SOL:

    如果进行删除一个点,将其加入到另一个集合时。

    会发现如果这个点是开始那个集合中的非叶子点,就很麻烦了。因为“上有老下有小”,“牵一发而动全身”

    但如果为叶子点,那就好办多了。

    于是对于每个点i,给它“上面”再加一个点N+i出来

    这样点i就不会是叶子点了

    为了维护这个性质,当点i与点j合并时,我们同时也要保持点J的“叶子”的状态

    于是设i的父亲点为N+j就好了。

    //假设有5个点,则对1到5这些点,每个点上面还有一个N+i的点
    //1上面是6,2上面是7,3上面是8,4上面是9,这样就可以保证我们在删除点的时候
    //不会删除根结点了
    //如果将1,2,3并在一个集合里面,则(1,2,3,6,7,8)就在一个集合中了
    //如果要移动1到4那集合,则先消除它在原集合中的影响 
    //然后将1的父亲设为9 
    #include <cstdio>
    #include <cstring>
    const int N = 200005;
    int n, m, parent[N], num[N], sum[N];
     
    int find(int x) 
    {
        return x == parent[x] ? x : parent[x] = find(parent[x]);
    }
    void init() {
        for (int i = 0; i <= n; i++) 
    	{
    	parent[i] = parent[i + n] = i + n;
    	sum[i] = sum[i + n] = i;
    	num[i] = num[i + n] = 1;
        }
    }
    int main() 
    {
        while (~scanf("%d%d", &n, &m)) 
    	{
    	int q, a, b;
    	init();
    	while (m--) {
    	    scanf("%d", &q);
    	    if (q == 1) {
    		scanf("%d%d", &a, &b);
    		int pa = find(a);
    		int pb = find(b);
    		if (pa == pb) continue;
    		parent[pa] = pb;
    		num[pb] += num[pa];
    		sum[pb] += sum[pa];
    	    }
    	    else if (q == 2) {
    		scanf("%d%d", &a, &b);
    		int pa = find(a);
    		int pb = find(b);
    		if (pa == pb) continue;
    		parent[a] = pb;
    		num[pa]--;
    		num[pb]++;
    		sum[pa] -= a;
    		sum[pb] += a;
    	    }
    	    else {
    		scanf("%d", &a);
    		int pa = find(a);
    		printf("%d %d
    ", num[pa], sum[pa]);
    	    }
    	}
        }
        return 0;
    }
    

      

     Sol:

    开始时如最一般的并查集一样开点,对于涉及移动一个元素到另一个集合的操作即第二种操作时.由于不知道这个元素是根结点还是一般的叶子点(叶子点可以直接移过去,所以可以将这个点的在原集合中的影响力清零,然后另新开一个结点将其copy过去,再将新结点并到目标集合中去。

    #include <iostream>
    #include <cstdio>
    using namespace std;
    #define maxn 200018
    int father[maxn],idx[maxn],num[maxn];
    //num来多少个,idx才存
    long long int sum[maxn];
    int n,m,cnt;
    int find(int x)
    {
        if(x==father[x])return x;
        return find(father[x]);
    }
    void init()
    {
        for(int i=1;i<=n;i++)
        {
            father[i]=idx[i]=sum[i]=i;
            num[i]=1;
        }
        cnt=n;
    }
    void Union(int p,int q)
    {
        int pp=find(idx[p]),qq=find(idx[q]);
        //统一使用idx[i]代表i目前在哪个集合中
        father[pp]=qq;
        num[qq]+=num[pp];
        sum[qq]+=sum[pp];
    }
    void Delete(int p)
    {
        int pp=idx[p];
        //取出p所在集合的真实的编号,因为存在删除操作,p所在的集合编号是不断变化的
        sum[find(pp)]-=p; //消除其影响力
        num[find(pp)]--;
        idx[p]=++cnt; //新加一个集合出来
        sum[idx[p]]=p; //以下如传统操作一样
        num[idx[p]]=1;
        father[idx[p]]=idx[p];
    }
    int main()
    {
        while(scanf("%d%d",&n,&m)==2)
        {
            init();
            int ope,p,q;
            for(int i=1;i<=m;i++)
            {
                scanf("%d",&ope);
                if(ope==1)
                {
                    scanf("%d%d",&p,&q);
                    if(find(idx[p])==find(idx[q]))
                    continue;
                    else
                    Union(p,q);
                }
                else if(ope==2)
                {
                    scanf("%d%d",&p,&q);
                    if(find(idx[p])!=find(idx[q]))
                    {
                        Delete(p);
                        Union(p,q);
                    }
                }
                else
                {
                    int u;
                    scanf("%d",&u);
                    int fuck=find(idx[u]);
                    printf("%d %lld ",num[fuck],sum[fuck]);
                    //这里我用I64d既然WA。。。
                }
            }
        }
        return 0;
    }

     另一个题目:Hdu2473

    5 6//5个数字(编号从0开始),6个操作
    M 0 1//将0与1所在集合,进行合并
    M 1 2//将2与1所在集合,进行合并
    M 1 3//将3与1所在集合,进行合并
    S 1//将1从所在集合中抽出来
    M 1 2
    S 3
    3 1 //第二组数据了
    M 1 2
    0 0

    Sample Output

    Case #1: 3 //[0,1,2],[3],[4]这三个集合
    Case #2: 2

    Sol:感觉没什么好写的。就是开虚点吧
    最开始时每个数字所在集合的编号就是其本身的值
    但涉及“踢"操作的时候,就将那个点的集合编号换成另一个没有用过的编号。

    #include<iostream>
    #include<string.h>
    #include<stdlib.h>
    #include<algorithm>
    #include<set>
    using namespace std;
    int par[100005];
    int vis[100005];//记录节点 
    int flag;
    set<int> S;
    void init(int x) 
    {
    	for (int i = 0; i<x; i++) 
    	{
    		vis[i] = i;
    	}
    }
    int find(int x) 
    {
    	if (par[x] == x) 
    	    return x;
    	else 
    		return par[x]=find(par[x]);
    }
    void unite(int x, int y) 
    {
    	int a = find(x);
    	int b = find(y);
    	if (a == b)
    	{
    			return ;
    		}
    	else 
    	{
    		par[a] = b;
    	}
    	return ;
    }
    int main()
    {  
        int n,m;  
        int sase = 0;  
        while(scanf("%d%d",&n,&m))
    	{  
            if(n == 0 && m == 0) break;  
            sase++;  
            int cnt = n;  
            for(int i = 0;i < 100005;i++) 
    		   par[i] = i;  
            for(int i = 0;i < n;i++) 
    		   vis[i] = i;  
            while(m--)
    		{  
                char s[3];  
                int a,b;  
                scanf("%s",s);  
                if(s[0] == 'M')
    			{  
                    scanf("%d%d",&a,&b);  
                    unite(vis[a],vis[b]);
                }  
                else{  
                    scanf("%d",&a);  
                    vis[a] = cnt++;  
                }  
            }  
            S.clear();  
            for(int i = 0;i < n;i++)
    		{  
                S.insert(find(vis[i]));  
    //对比一般并查集的写法..if(a[i]==i)ans++,可知vis[]数组的作用 } printf("Case #%d: %d ",sase,S.size()); } return 0; } ———————————————— 版权声明:本文为CSDN博主「Hugo5332」的原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接及本声明。 原文链接:https://blog.csdn.net/xxxslinyue/article/details/55259798

     Sol:

    正确的方法是每一个点都设立一个虚拟父亲比如1,2,3的父亲分别是4,5,6,现在合并1,2,3都在一个集合,那他们的父亲都是4,现在删除1,那就给1重新申请一个节点7

    现在2,3的父亲是4,1的父亲是7,删除成功。

    #include <stdio.h>
    #include <algorithm>
    #include <math.h>
    #include <string.h>
    #include <vector>
    #include <queue>
    #include <map>
    #include <stack>
    #include <iostream>
    #define pi acos(-1.0)
    #define INF 0x3f3f3f3f
    using namespace std;
    #define ll long long
    const int maxn=5000010;
    int pre[maxn],id,vis[maxn];
    int found(int x)
    {
        if(x!=pre[x]) 
    	    pre[x]=found(pre[x]);
        return pre[x];
    }
    void Merge(int a,int b)
    {
        int fx=found(a),fy=found(b);
        if(fx!=fy)
            pre[fx]=fy;
    }
    void del(int x)
    {
        pre[x]=id++;
    }
    int main()
    {
       
        int n,m,Case=0;
        while(scanf("%d%d",&n,&m),n+m)
        {
            for(int i=0;i<=n;i++)
                pre[i]=i+n;
            for(int i=n;i<=n+n+m;i++) 
            //本只有N个点,每个点多开一个“上面的”点出来
    		//另还有M次操作,预先再多开出M个点来 
                pre[i]=i;
            id=n+n;
            int a,b; char ch[5];
            for(int i=0;i<m;i++)
            {
                scanf("%s",ch);
                if(ch[0]=='M')
                {
                    scanf("%d%d",&a,&b);
                    Merge(a,b);
                }
                else
                {
                    scanf("%d",&a);
                    del(a);
                }
            }
            int ans=0;
            memset(vis,0,sizeof vis);
            for(int i=0;i<n;i++)
            {
                int x=found(i);
                if(!vis[x])
                    ans++,vis[x]=1;
            }
            printf("Case #%d: %d
    ",++Case,ans);
        }
        return 0;
    }
    

      

  • 相关阅读:
    人民币大小写转换
    清除vs中的最近打开的项目列表
    OWC绘图控件研究
    Fat32的磁盘格式转成Ntfs磁盘格式
    Host "localhost " is not allowed to connect to mysql server解决方法
    OWC中组合图的开发(柱状图+折线图)
    mysql 1251错误解决方法
    使用OWC做统计图图标样式
    完全卸载MYSQL
    nginx 伪静态
  • 原文地址:https://www.cnblogs.com/cutemush/p/12466364.html
Copyright © 2011-2022 走看看