【NOI2002】银河英雄传说

来源:互联网 发布:java编程思想.pdf 编辑:程序博客网 时间:2024/04/28 22:32

Overview

M=30000个点,N个操作(N500000)。
每次操作如下:
(1)M i j:将i所在的排列合并到j所在的排列;
(2)C i j:若ij不在同一排列,输出1,否则输出ij之间的数的个数。

Analysis

【STEP1】 要用在线算法

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

【STEP2】 暴力的想法

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

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

再考虑用数据结构来优化。
有合并操作,还要判断在不在一个集合中,想到了并查集。
在不在一个排列中很好解决,现在只用解决排列中两数间隔多少个数就可以了。

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

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

于是我们设两个数组:
f[i]i的父亲;
dis[i]i到父亲的距离。

试着使得各个操作得以满足维护的要求。
FindFather操作最容易处理,先不用管,先搞定合并操作。

Merge(x,y):将x所在的排列接在y
首先求出fx,fy,然后将fx接在fy后,f[fx]=fy
但是dis[fx]怎么办?
dis[fx]=size[fy],这就是说,我们要维护当前每个集合的元素个数即可。
再设一个数组size[i],当i不是集合代表元时没有意义,当i是集合代表元时表示这个集合的大小。

【STEP5】 整理思路

于是我们现在一共定义3个数组:f[N]dis[N]size[N]
对各个操作加以维护即可。

FindFather(i):路径压缩点i
1 若i=f[i]则不用处理,返回代表元i
2 记pre=f[i],anc=FindFather(pre)
3 则f[i]=anc,dis[i]+=dis[pre]

Merge(x,y):将x所在的排列接在y的排列后
1 fx=FindFather(x),fy=FindFather(y)
2 f[fx]=fy
3 dis[fx]=size[fy]
4 size[fy]+=size[fx]

Query(x,y):求xy的距离
1 fx=FindFather(x),fy=FindFather(y)
2 若fxfy,那么不在同一列,返回1
3 求|dis[x]dis[y]|1即可。

Attention

注意一个坑点,就是有30000个固定的点,而不是有n个。
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;}
0 0