洛谷P1196 [NOI2002] 银河英雄传说

 

题目大意

一个序列初始编号为 1,2,3,,,30000

有 2 个操作:

m i j 合并第 i 列和第 j 列,将第 i 列头部接到第 j 列尾部

c I j 询问 i 号和 j 号之间的数量,若不在同一列则输出-1

第一行有一个整数T1T5×105),表示总共有T条指令。

 

先分析一波

这个数据规模来看,必定是nlogn的复杂度

也就是说每个操作都是logn级别的才行

好像说了堆废话

这时,再结合题面中所给的合并操作,不难想到并查集

但是,明显普通并查集不好解决这个问题

那就用带权并查集啊

合并操作并不难,不是本题代码的关键,不过是思路中的关键

我们先考虑查询:本题难点是要查询 i,j 之间的数量而不是哪些数,

i 列和 j 列合并时,如果知道 i,j 列的数量,

则合并后,j 列中的数不受影响,i 列的每个数前面的数的个 数增加 j 列的总数。

我们需要知道每列的信息,用并查集来维护每列,

并查集每个集合维护两个值:集合总 数 count、集合中元素前面有多少元素 bef。

显然,开始时,count[i] = 1,bef[i] = 0

到此为止,基本上已经可以构建出一个框架了

最主要的函数则是并查集的find_()和merge()操作,

后者因为题目里的一些原因,被我写在了主函数里,可自行寻找

#include<bits/stdc++.h>
using namespace std;
int t,fa[30010],cnt[30010],bef[30010];
int find_(int x){
    if(fa[x]==x)return x;
    int fx = find_(fa[x]);
    bef[x]+=bef[fa[x]];
    return fa[x] = fx;
}
int main(){
    scanf("%d",&t);
    for(int i = 1;i<=30000;i++){
        fa[i] = i;
        cnt[i] = 1;
    }
    for(int i = 1;i<=t;i++){
        int x,y;char ch;
        cin>>ch;
        scanf("%d%d",&x,&y);
        int fx = find_(x);
        int fy = find_(y);
        if(ch=='M'){    
            fa[fx] = fy;
            bef[fx] = bef[fy]+cnt[fy];
            cnt[fy]+=cnt[fx];
        }else{
            if(fx!=fy) puts("-1");
            else printf("%d\n",abs(bef[x]-bef[y])-1);
        }
    }
    return 0;
}

非常的简短噢

posted @ 2022-12-24 10:42  cztq  阅读(41)  评论(0)    收藏  举报