洛谷 1196 题解

题意简述

30000 30000 30000个队列,初始每个队列里有元素 1 , 2 , 3 ⋯ 30000 1,2,3\cdots 30000 1,2,330000。两种指令:
1. M M M i i i j j j 编号为 i i i的队列整个接到 j j j
2. C C C i i i j j j 问编号为 i i i, j j j的两个点之间(不包含 i i i, j j j)有多少点。(如果不在一个队列里输出 − 1 -1 1

数据

输入
4
M 2 3
C 1 2
M 2 4
C 4 2
输出
-1
1

思路

这个题目是我在体育课上完成的思路,回来写的代码。。。
趁着刚做完,讲讲我思考的过程。
刚看到这个题的时候,第一个想到的肯定是并查集。但是突然注意到询问是要求中间有多少点,一脸懵逼:这怎么维护?难道是上Splay??(珂是Splay我不会啊。。。)

慢慢分析这个题,我们肯定不能暴力维护点的位置。想想那些区间求和的问题,都怎么做的:没错, 前 缀 和 \color{red}前缀和 !我们用 S u m [ i ] Sum[i] Sum[i]表示 i i i这个点在 i i i的队列中前面(不包括 i i i)有多少点。在询问的时候,只要将两个 S u m Sum Sum值取绝对值相减,然后 ± 1 \pm1 ±1或者不变就珂以得到了。究竟是哪个呢?经过对样例的模拟,我们会发现此时应该是 − 1 -1 1才是正确的。

上面这个看起来很好理解。那么如果我们要合并两个队列的时候,比方说是 x x x队列和 y y y队列,那么 x x x队列的每个元素的 s u m sum sum都因为接在了 y y y队列的后面,所以每个都要 + = C n t [ y ] +=Cnt[y] +=Cnt[y],其中 C n t [ y ] Cnt[y] Cnt[y]表示 y y y队列的元素个数。由于我们需要修改所有 x x x队列中的元素,遍历就不珂避免了,一次合并操作就要 O ( n ) O(n) O(n)了。怎么优化这个呢?

我们想想我们在搞并查集问题的时候都是怎么搞的:一个祖先一一对应一个集合。一个集合中所有的记录工作都 交 给 祖 先 来 完 成 \color{red}交给祖先来完成 。所以我们考虑这样做:只对祖先的 S u m Sum Sum加值,在查找点 i i i的时候逐个加上父亲(不包括祖先)的 S u m Sum Sum值,就是点 i i i实际的 S u m Sum Sum值。然后发现查找就 O ( n ) O(n) O(n)了,因为我们如果进行路径压缩,信息就丢失了。而实际上,我们珂以在 路 径 压 缩 的 时 候 就 更 新 i 点 的 S u m \color{red}路径压缩的时候就更新i点的Sum iSum值,然后让 i i i就算直接接到祖先上,也不会丢失数据,就保证了信息的正确性。这很好办,只要在路径压缩回溯的路上顺便加上这个值即可。

这样一次操作就 O ( 1 ) O(1) O(1)了!!!

代码:

#include<bits/stdc++.h>
#define N 1001000
using namespace std;
class DSU
{
    private:
        int Father[N];
    public:
        int Cnt[N],Sum[N];
        //和上面说的一样.Cnt记录集合大小,Sum记录前面多少点
        //注意这个"前面"是不包括i本身的
        void Init()
        {
            for(int i=1;i<N;i++)
            {
                Father[i]=i;
                Cnt[i]=1;
                Sum[i]=0;//所以这边是0
            }
        }
        int Find(int x)
        //找到x的祖先,并将x的Sum值设置成所有父亲(当然,不包括祖先)
        {
            if (Father[x]==x) return x;//不包括祖先,所以此时直接返回x

            int ax=Find(Father[x]);
            Sum[x]+=Sum[Father[x]];//Father[x]在经过一轮Find后,Sum[Father[x]]已经设置成了Father[x]所有父亲(不包括祖先)的Sum和。然后只要用这一行代码,就珂以将Sum[x]的值设置成x所有父亲(不包括祖先)的Sum和了。

            return Father[x]=ax;//此时已经记录好了,珂以路径压缩了(准确来讲,此时如果不路径压缩会得到错误答案。仔细想想)
        }
        void Merge(int x,int y)//队列x接到队列y
        {
            int ax=Find(x),ay=Find(y);

            Sum[ax]+=Cnt[ay];//记录好Sum(只给祖先加上)
            Cnt[ay]+=Cnt[ax];//Cnt也要记录好
            Father[ax]=ay;//祖先接过去
        }
}D;

void Query()
{
    int T;scanf("%d",&T);
    while(T--)
    {
        char o[3];int x,y;
        scanf("%s%d%d",o,&x,&y);
        if (o[0]=='M')
        {
            D.Merge(x,y);
        }
        else
        {
            int ax=D.Find(x),ay=D.Find(y);
            if (ax!=ay)
            //不在一个队列里
            {
                printf("-1\n");
            }
            else
            //在一个队列里
            {
                printf("%d\n",abs(D.Sum[x]-D.Sum[y])-1);
            }
        }
    }
}

main()
{
    D.Init();
    Query();
    return 0;
}
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值