Overview
有
M=30000
个点,
N
个操作(
每次操作如下:
(1)
M
(2)
Analysis
【STEP1】 要用在线算法
这道题的多个操作,感觉上就是在线维护了。
【STEP2】 暴力的想法
首先考虑暴力维护,合并操作用一个 next 数组就可以实现,但是判断在不在一个排列,排列中有多少个数,一次要 O(n) ,很明显不行。
【STEP3】 用并查集优化暴力算法
再考虑用数据结构来优化。
有合并操作,还要判断在不在一个集合中,想到了并查集。
在不在一个排列中很好解决,现在只用解决排列中两数间隔多少个数就可以了。
【STEP4】 试着求解来扩充并查集
现在要扩充并查集。
并查集求区间的方法,通常是维护前缀和,用区间减法。
于是我们设两个数组:
f[i]
:
i
的父亲;
试着使得各个操作得以满足维护的要求。
Merge(x,y)
:将
x
所在的排列接在
首先求出
fx,fy
,然后将
fx
接在
fy
后,
f[fx]=fy
。
但是
dis[fx]
怎么办?
dis[fx]=size[fy]
,这就是说,我们要维护当前每个集合的元素个数即可。
再设一个数组
size[i]
,当
i
不是集合代表元时没有意义,当
【STEP5】 整理思路
于是我们现在一共定义
3
个数组:
对各个操作加以维护即可。
①
FindFather(i)
:路径压缩点
i
1 若
2 记
3 则
f[i]=anc,dis[i]+=dis[pre]
;
②
Merge(x,y)
:将
x
所在的排列接在
1
fx=FindFather(x),fy=FindFather(y)
;
2
f[fx]=fy
;
3
dis[fx]=size[fy]
;
4
size[fy]+=size[fx]
;
③
Query(x,y)
:求
x
到
1
fx=FindFather(x),fy=FindFather(y)
;
2 若
fx≠fy
,那么不在同一列,返回
−1
;
3 求
|dis[x]−dis[y]|−1
即可。
Attention
注意一个坑点,就是有
30000
个固定的点,而不是有
n
个。
Sumarize
PS:话说NOI连续两年出并查集了……
然而是很早以前……
突然想起NOI2015用并查集坑了T1的9个点,2333333
这道题首先给了我一个启发,就是我们在求解问题的时候,可以先想一下暴力算法,再从数据结构、算法优化等各种角度加以优化。
这样不仅是一种好的方法,而且也有一个程序来对拍。
然后就是这类并查集问题:并查集可以将序列用链划分,同一条链上可以用区间减法求区间和。
最后就是在尝试求解的过程中,很可能会遭到失败。这时候不要全盘否定,要学会调整,增加几个变量。
例如本题发现
merge
操作时
f[x]
无法求,我们考虑再加一个数组
next
。
Code
#include <cstdio>
#include <cctype>
#include <cmath>
const int M=30000;
int n;
int f[M+10],dis[M+10];
int size[M+10];
inline int read(void)
{
int x=0; char c=getchar();
for (;!isdigit(c);c=getchar());
for (;isdigit(c);c=getchar()) x=x*10+c-'0';
return x;
}
int findf(int i)
{
if (f[i]==i) return i;
int pre=f[i],anc=findf(f[i]);
f[i]=anc,dis[i]+=dis[pre];
return anc;
}
int main(void)
{
n=read();
for (int i=1;i<=M;i++) f[i]=i,size[i]=1;
char c; int x,y; int fx,fy;
for (int i=1;i<=n;i++)
{
scanf("\n"); c=getchar(); x=read(),y=read();
fx=findf(x),fy=findf(y);
if (c=='M')
{
f[fx]=fy;
dis[fx]=size[fy];
size[fy]+=size[fx];
}
else printf("%d\n",fx!=fy?-1:dis[x]<dis[y]?dis[y]-dis[x]-1:dis[x]-dis[y]-1);
}
return 0;
}