您的位置:首页 > 其它

【NOI2002】银河英雄传说

2016-01-29 16:39 387 查看

Overview

有M=30000M=30000个点,NN个操作(N≤500000N\leq 500000)。

每次操作如下:

(1)MM ii jj:将ii所在的排列合并到jj所在的排列;

(2)CC ii jj:若ii,jj不在同一排列,输出−1-1,否则输出ii与jj之间的数的个数。

Analysis

【STEP1】 要用在线算法

这道题的多个操作,感觉上就是在线维护了。

【STEP2】 暴力的想法

首先考虑暴力维护,合并操作用一个nextnext数组就可以实现,但是判断在不在一个排列,排列中有多少个数,一次要O(n)O(n),很明显不行。

【STEP3】 用并查集优化暴力算法

再考虑用数据结构来优化。

有合并操作,还要判断在不在一个集合中,想到了并查集。

在不在一个排列中很好解决,现在只用解决排列中两数间隔多少个数就可以了。

【STEP4】 试着求解来扩充并查集

现在要扩充并查集。

并查集求区间的方法,通常是维护前缀和,用区间减法。

于是我们设两个数组:

f[i]f[i]:ii的父亲;

dis[i]dis[i]:ii到父亲的距离。

试着使得各个操作得以满足维护的要求。

FindFatherFindFather操作最容易处理,先不用管,先搞定合并操作。

Merge(x,y)Merge(x,y):将xx所在的排列接在yy后

首先求出fx,fyfx,fy,然后将fxfx接在fyfy后,f[fx]=fyf[fx]=fy。

但是dis[fx]dis[fx]怎么办?

dis[fx]=size[fy]dis[fx]=size[fy],这就是说,我们要维护当前每个集合的元素个数即可。

再设一个数组size[i]size[i],当ii不是集合代表元时没有意义,当ii是集合代表元时表示这个集合的大小。

【STEP5】 整理思路

于是我们现在一共定义33个数组:f[N]f
,dis[N]dis
,size[N]size


对各个操作加以维护即可。

①FindFather(i)FindFather(i):路径压缩点ii

1 若i=f[i]i=f[i]则不用处理,返回代表元ii;

2 记pre=f[i],anc=FindFather(pre)pre=f[i],anc=FindFather(pre);

3 则f[i]=anc,dis[i]+=dis[pre]f[i]=anc,dis[i]+=dis[pre];

②Merge(x,y)Merge(x,y):将xx所在的排列接在yy的排列后

1 fx=FindFather(x),fy=FindFather(y)fx=FindFather(x),fy=FindFather(y);

2 f[fx]=fyf[fx]=fy;

3 dis[fx]=size[fy]dis[fx]=size[fy];

4 size[fy]+=size[fx]size[fy]+=size[fx];

③Query(x,y)Query(x,y):求xx到yy的距离

1 fx=FindFather(x),fy=FindFather(y)fx=FindFather(x),fy=FindFather(y);

2 若fx≠fyfx\neq fy,那么不在同一列,返回−1-1;

3 求|dis[x]−dis[y]|−1|dis[x]-dis[y]|-1即可。

Attention

注意一个坑点,就是有3000030000个固定的点,而不是有nn个。

nn是操作数量!!!!

Sumarize

PS:话说NOI连续两年出并查集了……

然而是很早以前……

突然想起NOI2015用并查集坑了T1的9个点,2333333

这道题首先给了我一个启发,就是我们在求解问题的时候,可以先想一下暴力算法,再从数据结构、算法优化等各种角度加以优化

这样不仅是一种好的方法,而且也有一个程序来对拍。

然后就是这类并查集问题:并查集可以将序列用链划分,同一条链上可以用区间减法求区间和。

最后就是在尝试求解的过程中,很可能会遭到失败。这时候不要全盘否定,要学会调整,增加几个变量。

例如本题发现mergemerge操作时f[x]f[x]无法求,我们考虑再加一个数组nextnext。

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;
}
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: