【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; }
相关文章推荐
- Android 内存溢出和内存泄漏的区别
- ASP.NET Uploadify 上传文件过大报错
- hdoj--2015--偶数求和(水题)
- CKEditor图片上传实现详细步骤(使用Struts 2)
- Android-测试技巧 – 你所不知道的测试黑科技
- Zoho Recruit:招聘管理系统有什么用
- Lind.DDD.Caching分布式数据集缓存介绍
- hibernate里createSQLQuery
- spring-boot支持双数据源mysql+mongo
- LeetCode64. Minimum Path Sum
- Django学习笔记(模型对照表)
- java28:集合类2
- 使用UEFI BIOS Updater(UBU)来更新CPU微代码
- socket常见几种异常
- 分布式服务框架DUBBO
- struts2中的constant配置详解
- 石牛寨杜甫墓祠一日游
- 步步理解 JAVA 泛型编程(一)
- 为什么要用『新技术』?
- Mahout系列之Canopy聚类算法分析