UVa11987

题意:模拟三种操作

思路:按题意模拟即可,麻烦的将p移动到q的集合,不能直接将p的父结点改为q的父结点,因为p可能是某个集合的根节点。解决的方法是不移动p结点,而维护集合的个数和集合元素和的关系,如果将p加入q的集合,则另外增加一个新的结点加入q的集合,而原来的p结点就变成了一个虚拟结点,对结果没有影响。所以开辟一个id数组记录改变后的结点的位置,使用时都从id数组中取数。

#include<iostream>
#include<cstdio>
#include<string.h>
#include<stack>
#include<set>
#include<map>
#include<vector>
#include<algorithm>
#include<cstring>
#include<queue>
using namespace std;
const int maxn = 1000005;
int n, m;
int P[maxn];
int Sum[maxn];  //集合的和
int Tot[maxn];  //集合的个数
int id[maxn];   //id[i]代表结点i当前的位置
int pos;

void maketree()
{
    for (int i = 0; i < maxn; i++)
    {
        P[i] = i;
        Sum[i] = i;
        Tot[i] = 1;
        id[i] = i;
    }
    pos = n;
}

int Find(int x)
{
    if (x != P[x])
        return P[x] = Find(P[x]);
    return P[x];
}

//将y连接到x上
void unite(int x, int y)
{
    int p1 = Find(x);
    int p2 = Find(y);
    if (p1 != p2)
    {
        P[p2] = p1;
        Sum[p1] += Sum[p2];
        Tot[p1] += Tot[p2];
    }
}

//由于是根节点时不能直接移动,所以新开辟了一个id数组记录数字i最后的位置
//将数字i移动到集合S
void Move(int i, int S)
{
    int p1 = Find(id[i]);
    int p2 = Find(S);
    if (p1 != p2)
    {//原结点变成了虚拟根结点,对结果无影响
        Tot[p1]--;
        Tot[p2]++;
        Sum[p1] -= i;
        Sum[p2] += i;
        id[i] = ++pos;
        P[pos] = p2;    //给结点i新分配一个结点下标,放在原数组的后面
    }

}

int main()
{
    while (scanf("%d %d", &n, &m) != EOF)
    {
        maketree();
        for (int i = 0; i < m; i++)
        {
            int c1, p, q;
            scanf("%d %d", &c1, &p);
            if (c1 == 1)
            {
                scanf("%d", &q);
                unite(id[q], id[p]);
            }
            else if (c1 == 2)   //不能直接让p指向新的集合(有可能时根节点)
            {
                scanf("%d", &q);
                Move(p, id[q]);
            }
            else
            {
                int root = Find(id[p]);
                printf("%d %d\n", Tot[root], Sum[root]);
            }
        }
    }

    return 0;
}

猜你喜欢

转载自blog.csdn.net/qq_39479426/article/details/81613105