hdu2818与hdu3635 (带权并查集)

hdu2818

约翰正在玩积木。有N个block (1 <= N <= 30000)编号为1…N .最初有N根桩,每根桩包含一个块。然后John做一些运算P次(1 <= P <= 1000000)。有两种操作:
M x y:把包含X块的整个堆放到包含Y块的堆上,如果X和Y在同一堆上,就忽略这个命令。
C x计算方块X下的方块数
要求您找出每个C操作的输出。

输入

第一行包含整数P,然后是P行,每一行包含上面描述的操作。

输出

在一行中输出每个C操作的计数。

Sample Input

6
M 1 6
C 1
M 2 4
M 2 6
C 3
C 4

Sample Output

1
0
2

分析:

用d[i]表示i下方的石子个数
用num[i]表示i集合中总石子个数
pre[i]表示集合中 i 的父节点
这题有点不一样,根节点应该是最底下的石子(是我思维定式了)。

1.合并操作
因为题目是把x堆放到y堆上,所以y应该是新根,所以pre[x]=y
同时x下方的石子数量增多,因此d[x]+=num[y]
集合总石子数增多,因此num[y]+=num[x]
那么问题来了,d[x]+=num[y]操作只更新了x一个石子,x上方的石子数量没有更新啊?
这点可以在并查集查根的过程中操作(类似线段树的lazy操作,等到查询的时候再更新)
因此:

2.查询操作
如果当前节点x的根为自己本身(pre[x]==x),毫无疑问直接return x
如果当前节点不是根节点:
从操作1可知,我们在原来的根节点x处做了d[x]=num[y]
原先的x集合中每一个元素的d[]都应该加上num[y]
我们知道当一个节点为根节点的时候,d[i]=0,
因此d[x]的值就是原来x集合中每个元素应该加上的值(这时候d[x]=num[y])
一步一步解析:

//假设根为y
int ffind(int a){
    if(pre[a]==a){//如果当前节点就是根直接返回
        return a;
    }else{
        int t=ffind(pre[a]);//找到根节点y并且存在t中,用于后面路径压缩
        d[a]+=d[pre[a]];//因为上面一步的递归操作,这时候的pre[a]就是x,直接加上d[x]就行了
        return pre[a]=t;//路径压缩,a直接指向y
    }
}
code:
#include<iostream>
#include<cstdio>
#include<cstring>
#include<cmath>
#include<queue>
#include<algorithm>
#define set0(a) memset(a,0,sizeof(a))
#define set1(a) memset(a,-1,sizeof(a))
#define setinf(a) memset(a,0x3f3f3f3f,sizeof(a))
#define ll long long
#define P pair<int,int>
#define mp(a,b) make_pair(a,b)
const int inf=0x3f3f3f3f;
const int inn=0x80808080;
using namespace std;
const int maxm=5e4+5;
int pre[maxm];//pre[i]表示i的下面一层
int d[maxm];//d[i]表示i下面的石块总数(不包括本身)
int num[maxm];//num[i]表示i所在堆的石块总数
void init(){
    for(int i=0;i<maxm;i++){
        pre[i]=i;
        d[i]=0;
        num[i]=1;
    }
}
int ffind(int x){
    if(pre[x]==x){
        return x;
    }else{
        int t=ffind(pre[x]);
        d[x]+=d[pre[x]];
        return pre[x]=t;
    }
}
int main(){
    init();
    int n;
    scanf("%d",&n);
    while(n--){
        char s[5];
        scanf("%s",s);
        if(s[0]=='M'){
            int a,b;
            scanf("%d%d",&a,&b);
            int x=ffind(a);
            int y=ffind(b);
            if(x!=y){
                pre[x]=y;//把y放到x的下面,则更新y为根节点
                d[x]+=num[y];//因为y集合再x的下面,更新d[x]
                num[y]+=num[x];//集合总石子个数更新(这里y已经是根节点了)
            }
        }else{
            int a;
            scanf("%d",&a);
            ffind(a);//更新某些还没更新的地方
            printf("%d\n",d[a]);
        }
    }
    return 0;
}

hdu3635

五百年后,龙珠的数量将会出乎意料地增加,所以对孙悟空来说把所有的龙珠聚集在一起太难了。
他的国家有N个城市,而世界上正好有N个龙珠。首先,为了第i个龙珠,神圣的龙会把它放在第i个城市。经过漫长的岁月,一些城市的龙珠会被运送到其他城市。为了节省体力,悟空计划乘飞天云,一种神奇的飞天云来收集龙珠。
每次悟空收集一个龙珠的信息,他都会问你那个龙珠的信息。你必须告诉他球在哪个城市,在那个城市有多少个龙珠,你还需要告诉他球已经被运送了多少次。

输入

输入的第一行是一个正整数T(0 < T <= 100)。
对于每种情况,第一行包含两个整数:N和Q (2 < N <= 10000, 2 < Q <= 10000)。
下列Q行每一行都包含一个事实或一个问题,格式如下:
所有和A在同一个城市的龙珠都被运到了B球所在的城市。你可以假设这两个城市是不同的。
问:悟空想知道X (Ath球所在城市的id), Y(第X个城市的球数),Z (Ath球的转运时间)。(1 <= A, B <= N)

输出

对于每个测试用例,输出作为示例输出形式的测试用例号。然后,对于每个查询,输出一行三个由空格分隔的整数X Y Z。

分析:

类似上一题
cnt[]数组存转移次数,
num[]记录集合中龙珠的数量
合并和查询更新都和上一题基本一致

code:
#include<iostream>
#include<cstdio>
#include<cstring>
#include<cmath>
#include<queue>
#include<algorithm>
#define set0(a) memset(a,0,sizeof(a))
#define set1(a) memset(a,-1,sizeof(a))
#define setinf(a) memset(a,0x3f3f3f3f,sizeof(a))
#define ll long long
#define P pair<int,int>
#define mp(a,b) make_pair(a,b)
const int inf=0x3f3f3f3f;
const int inn=0x80808080;
using namespace std;
const int maxm=1e4+5;
int pre[maxm];
int num[maxm];
int cnt[maxm];
void init(int n){
    for(int i=0;i<=n;i++){
        pre[i]=i;
        cnt[i]=0;
        num[i]=1;
    }
}
int ffind(int x){
    if(pre[x]==x){
        return x;
    }else{
        int t=ffind(pre[x]);
        cnt[x]+=cnt[pre[x]];
        return pre[x]=t;
    }
}
int main(){
    int T;
    cin>>T;
    int cas=1;
    while(T--){
        int n,q;
        cin>>n>>q;
        init(n);
        printf("Case %d:\n",cas++);
        while(q--){
            char s[5];
            scanf("%s",s);
            if(s[0]=='T'){
                int a,b;
                scanf("%d%d",&a,&b);
                int x=ffind(a);
                int y=ffind(b);
                if(x!=y){
                    pre[x]=y;
                    num[y]+=num[x];
                    cnt[x]++;
                }
            }else{
                int a;
                scanf("%d",&a);
                int x=ffind(a);
                printf("%d %d %d\n",x,num[x],cnt[a]);
            }
        }
    }
    return 0;
}
  • 2
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值