题意简述
有
30000
30000
30000个队列,初始每个队列里有元素
1
,
2
,
3
⋯
30000
1,2,3\cdots 30000
1,2,3⋯30000。两种指令:
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 路径压缩的时候就更新i点的Sum值,然后让 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;
}