codevs 1477 永无乡

http://codevs.cn/problem/1477/

2012年湖南湖北省队选拔赛

 时间限制: 1 s
 空间限制: 128000 KB
 
题目描述 Description

永无乡包含 n 座岛,编号从 1 到 n,每座岛都有自己的独一无二的重要度,按照重要度可
以将这 n 座岛排名,名次用 1 到 n 来表示。某些岛之间由巨大的桥连接,通过桥可以从一个岛
到达另一个岛。如果从岛 a 出发经过若干座(含 0 座)桥可以到达岛 b,则称岛 a 和岛 b 是连
通的。现在有两种操作:B x y 表示在岛 x 与岛 y 之间修建一座新桥。Q x k 表示询问当前与岛
x 连通的所有岛中第 k 重要的是哪座岛,即所有与岛 x 连通的岛中重要度排名第 k 小的岛是哪
座,请你输出那个岛的编号。

输入描述 Input Description

从文件 input.txt 中读入数据,输入文件第一行是用空格隔开的两个正整数 n 和 m,分别
表示岛的个数以及一开始存在的桥数。接下来的一行是用空格隔开的 n 个数,依次描述从岛 1
到岛 n 的重要度排名。随后的 m 行每行是用空格隔开的两个正整数 ai 和 bi,表示一开始就存
在一座连接岛 ai 和岛 bi 的桥。后面剩下的部分描述操作,该部分的第一行是一个正整数 q,
表示一共有 q 个操作,接下来的 q 行依次描述每个操作,操作的格式如上所述,以大写字母 Q
或 B 开始,后面跟两个不超过 n 的正整数,字母与数字以及两个数字之间用空格隔开。
对于 20%的数据 n≤1000,q≤1000
对于 100%的数据 n≤100000,m≤n,q≤300000

输出描述 Output Description

输出文件 output.txt 中,对于每个 Q x k 操作都要依次输出一行,其中包含一个整数,表
示所询问岛屿的编号。如果该岛屿不存在,则输出-1。

样例输入 Sample Input

5 1
4 3 2 5 1
1 2
7
Q 3 2
Q 2 1
B 2 3
B 1 5
Q 2 1
Q 2 4
Q 2 3

样例输出 Sample Output

-1

2

5

1

2

本题卡cin啊啊啊啊啊啊啊啊啊!!!!!!

法一:启发式合并+并查集+splay

#include<cstdio>
#include<iostream>
#include<algorithm>
#define N 100100
using namespace std;
int n,m,p;
int key[N],tot,siz[N],ch[N][],fa[N],F[N];
int find(int x) {return x==F[x] ? F[x] : F[x]=find(F[x]);}
int read()
{
int x=;char c=getchar();
while(c<''||c>'') c=getchar();
while(c>=''&&c<='') {x=x*+c-'';c=getchar();}
return x;
}
int getson(int x)
{
return ch[fa[x]][]==x;
}
void update(int x)
{
if(!x) return;
siz[x]=siz[ch[x][]]+siz[ch[x][]]+;
}
void rotate(int x)
{
int y=fa[x],z=fa[y],kind=getson(x);
ch[y][kind]=ch[x][kind^];ch[x][kind^]=y;
fa[ch[y][kind]]=y;fa[y]=x;
fa[x]=z;
if(z) ch[z][ch[z][]==y]=x;
update(y);
}
void splay(int x)
{
for(int f;f=fa[x];rotate(x))
if(fa[f]) rotate(getson(x)==getson(f) ? f:x);
update(x);
}
void link(int x,int y)
{
int now=x;
while(ch[now][key[y]>key[now]]) now=ch[now][key[y]>key[now]];
fa[y]=now;
ch[now][key[y]>key[now]]=y;
siz[y]=;
update(now);
splay(y);
}
void merge(int now,int y)
{
int tmp1=,tmp2=;
if(ch[y][]) tmp1=ch[y][],ch[y][]=;
if(ch[y][]) tmp2=ch[y][],ch[y][]=;
link(now,y);
if(tmp1) merge(y,tmp1);
if(tmp2) merge(y,tmp2);
}
int query(int x,int k)
{
splay(x);
if(k>siz[x]) return -;
int now=x;
while()
{
int tmp=ch[now][] ? siz[ch[now][]] : ;
if(k<=tmp) now=ch[now][];
else
{
if(k==tmp+) return now;
else
{
now=ch[now][];
k-=tmp+;
}
}
}
}
int main()
{
n=read();m=read();
for(int i=;i<=n;i++)
{
siz[i]++;
key[i]=read();
F[i]=i;
}
int x,y,X,Y;
for(int i=;i<=m;i++)
{
x=read();y=read();
X=find(x);Y=find(y);
if(X!=Y)
{
F[X]=Y;
splay(X);splay(Y);
if(siz[X]<siz[Y]) swap(X,Y);
merge(X,Y);
}
}
p=read();
char c[];
for(int i=;i<=p;i++)
{
scanf("%s",c);
x=read();y=read();
if(c[]=='B')
{
X=find(x);Y=find(y);
if(X!=Y)
{
F[X]=Y;
splay(X);splay(Y);
if(siz[X]<siz[Y]) swap(X,Y);
merge(X,Y);
}
}
else
{
int t=query(x,y);
printf("%d\n",t);
}
}
}

2个错误:

1、

void update(int x)
{
  if(!x) return;
  siz[x]=siz[ch[x][0]]+siz[ch[x][1]]+1;
}

没有判断x是否为0

2、

void link(int x,int y)
{
  int now=x;
  while(ch[now][key[y]>key[now]]) now=ch[now][key[y]>key[now]];
  fa[y]=now;
  ch[now][key[y]>key[now]]=y;
  siz[y]=1;
  update(now);
  splay(y);
}

没有修改siz[y] 没有splay(y)

先修改siz[y]=1,防止y本身还带有左右孩子

同时 update(now) 更新now 的siz

splay(y)不是多余的操作,他的目的不是将y转到根节点

而是更改自y至根节点所在路径上的siz,splay操作恰好可以完成

同时为后续的merge 操作准备根节点

法二、启发式合并+并查集+线段树

#include<cstdio>
#include<iostream>
#define N 100001
using namespace std;
int n,m,p,key[N],tot,sa[N],fa[N],root[N];
struct node
{
int l,r,siz;
}tr[N*];
int find(int x) {return x==fa[x] ? fa[x] :fa[x]=find(fa[x]);}
void add(int & k,int l,int r,int x)
{
if(!k) k=++tot;
if(l==r) {tr[k].siz=;return;}
int mid=l+r>>;
if(x<=mid) add(tr[k].l,l,mid,x);
else add(tr[k].r,mid+,r,x);
tr[k].siz=tr[tr[k].l].siz+tr[tr[k].r].siz;
}
int merge(int x,int y)
{
if(!x) return y;
if(!y) return x;
tr[x].l=merge(tr[x].l,tr[y].l);
tr[x].r=merge(tr[x].r,tr[y].r);
tr[x].siz=tr[tr[x].l].siz+tr[tr[x].r].siz;
return x;
}
int query(int x,int l,int r,int k)
{
if(l==r) return l;
int mid=l+r>>,tmp=tr[tr[x].l].siz;
if(tmp>=k) return query(tr[x].l,l,mid,k);
else return query(tr[x].r,mid+,r,k-tmp);
}
int read()
{
int x=,f=;char ch=getchar();
while(ch<''||ch>''){if(ch=='-')f=-;ch=getchar();}
while(ch>=''&&ch<='') x=x*+ch-,ch=getchar();
return x*f;
}
int main()
{
n=read();m=read();
for(int i=;i<=n;i++) key[i]=read(),fa[i]=i,sa[key[i]]=i;
int x,y,r1,r2;
for(int i=;i<=m;i++)
{
x=read();y=read();
r1=find(x),r2=find(y);
fa[r1]=r2;
}
for(int i=;i<=n;i++)
{
int r=find(i);
add(root[r],,n,key[i]);
}
p=read();
char c[];int s;
for(int i=;i<=p;i++)
{
scanf("%s",c);
x=read();y=read();
if(c[]=='B')
{
r1=find(x);r2=find(y);
if(r1!=r2)
{
fa[r2]=r1;
merge(root[r1],root[r2]);
}
}
else
{
s=find(x);
if(tr[root[s]].siz<y) printf("-1\n");
else
{
s=query(root[s],,n,y);
printf("%d\n",sa[s]);
}
}
}
}

联想 动态线段树 SDOI2014 旅行http://www.cnblogs.com/TheRoadToTheGold/p/6394842.html

2个错误:

1、合并两颗线段树

错误代码:

void merge(int x,int y)
{
  if(!x&&!y) return;
  tr[x].siz+=tr[y].siz;
  merge(tr[x].l,tr[y].l);
  merge(tr[x].r,tr[y].r);
}

原因:当x=0,y不为0时 应把y的信息合并到x所对应的位置上,但由于x为0,y的信息合并不上

所以正确合并:类似于主席树中的共用子节点

int merge(int x,int y)
{
  if(!x) return y;
  if(!y) return x;
  tr[x].l=merge(tr[x].l,tr[y].l);
  tr[x].r=merge(tr[x].r,tr[y].r);
  tr[x].siz=tr[tr[x].l].siz+tr[tr[x].r].siz;
  return x;
}

2、并查集与线段树合并的结合

if(r1!=r2)
{
  fa[r2]=r1;
  merge(root[r1],root[r2]);
}

并查集中i的父亲置为j,i的信息就要往j上合

r1、r2对应顺序不能乱

反思一:

两种方法相对比:

1、并查集的作用

①splay合并不需要考虑并查集内的父子关系,因为splay合并2棵树时,都将代表节点转至根节点

并查集只起指示哪两个集合的作用

②线段树中并查集内部i的父亲指向j,线段树合并时,必须i所在线段树合并到j所在线段树

因为合并哪两颗线段树需要root[i]指定 ,而i的确定是通过并查集找的

如果线段树合并j合向i,但i在并查集中的父亲指向j,

那么再来合并i和k时,合并的两棵树原来的是j和k,原来的j仍然只有j的信息

为什么splay不需要呢?

因为splay合并之前,先将2棵树的代表节点转至根节点

如果j合向i,那么k无论合向j还是i,都会将j或i转至根节点,而i或j是真的在同一棵树上

一句话解释就是 splay 中节点标号就是节点标号,线段树中对节点进行了重新标号,所以需要依靠root数组确定线段树中的节点对应题目中的哪个节点

2、合并方式

① splay 小的合并到大的上,因为合并是暴力把一颗splay上的点一个一个插到另一颗splay上,所以插得点越少越好

② 形态相同(权且这样认为)2颗线段树直接合并

3、合并之后,对被合并的树的处理

①splay 把被合并的那一颗树的所有信息都清零,因为节点编号不变,节点到新的树上信息仍存在这个编号下

② 线段树不用管,因为这棵树被合并后相当于消失了,并查集的父子关系使他不可能被找到

反思二:

为什么不是主席树

简单理解:主席树维护链上信息,线段树维护一坨点的信息

联想SDOI 2013 森林 http://www.cnblogs.com/TheRoadToTheGold/p/6524904.html

森林选用的是主席树,因为他要确定两点间路径,从这条链上找第k值

今天一天只做了1道题,2个原因

① 以前学过的数据结构没有理解透

② 本题卡cin!!,单个字符输入,为避空格可以直接按字符串输入啊

对拍数据生成代码:

#include<cstdio>
#include<ctime>
#include<cstdlib>
using namespace std;
bool v[];
int main()
{
freopen("data","w",stdout);
srand(time());
int n=rand()%+,m=rand()%n;
n+=;
printf("%d %d\n",n,m);
for(int i=;i<=n;i++)
{
int a=rand()%n+;
while(v[a]) a=rand()%n+;
v[a]=true;
printf("%d ",a);
}
printf("\n");
for(int i=;i<=m;i++)
{
int a=rand()%n+,b=rand()%n+;
while(a==b) b=rand()%n+;
printf("%d %d\n",a,b);
}
int p=rand()%+;
printf("%d\n",p);
for(int i=;i<=p;i++)
{
if(i<=p/) printf("B ");
else printf("Q ");
int a=rand()%n+,b=rand()%n+;
while(a==b) b=rand()%n+;
printf("%d %d\n",a,b);
}
}

HNOI 2012 永无乡的更多相关文章

  1. BZOJ 2733 HNOI 2012 永无乡 平衡树启示式合并

    题目大意:有一些岛屿,一開始由一些无向边连接. 后来也有不断的无向边增加,每个岛屿有个一独一无二的重要度,问随意时刻的与一个岛屿联通的全部岛中重要度第k大的岛的编号是什么. 思路:首先连通性一定要用并 ...

  2. 解题:HNOI 2012 永无乡

    题面 并查集维护连通性,然后暴力启发式合并就完了,记得合并时边DFS边清空数组 #include<cstdio> #include<cstring> #include<a ...

  3. HNOI(湖南省选试题)——永无乡

    今天写了一道十分巧妙的数据结构题---永无乡 (看的题解......) 永无乡包含 n 座岛,编号从 1 到 n,每座岛都有自己的独一无二的重要度,按照重要度可 以将这 n 座岛排名,名次用 1 到 ...

  4. BZOJ 2733 【HNOI2012】 永无乡

    Description 永无乡包含 n 座岛,编号从 1 到 n,每座岛都有自己的独一无二的重要度,按照重要度可 以将这 n 座岛排名,名次用 1 到 n 来表示.某些岛之间由巨大的桥连接,通过桥可以 ...

  5. 【BZOJ-2733】永无乡 Splay+启发式合并

    2733: [HNOI2012]永无乡 Time Limit: 10 Sec  Memory Limit: 128 MBSubmit: 2048  Solved: 1078[Submit][Statu ...

  6. BZOJ 2733: [HNOI2012]永无乡 启发式合并treap

    2733: [HNOI2012]永无乡 Time Limit: 20 Sec Memory Limit: 256 MB 题目连接 http://www.lydsy.com/JudgeOnline/pr ...

  7. 2733: [HNOI2012]永无乡 - BZOJ

    Description 永无乡包含 n 座岛,编号从 1 到 n,每座岛都有自己的独一无二的重要度,按照重要度可 以将这 n 座岛排名,名次用 1 到 n 来表示.某些岛之间由巨大的桥连接,通过桥可以 ...

  8. bzoj 2733: [HNOI2012]永无乡 离线+主席树

    2733: [HNOI2012]永无乡 Time Limit: 10 Sec  Memory Limit: 128 MBSubmit: 1167  Solved: 607[Submit][Status ...

  9. BZOJ 2733: [HNOI2012]永无乡(treap + 启发式合并 + 并查集)

    不难...treap + 启发式合并 + 并查集 搞搞就行了 --------------------------------------------------------------------- ...

随机推荐

  1. BZOJ2693jzptab

    简单般Bzoj2154: Crash的数字表格 Sol 增加了数据组数T<=10000 推到 \(ans=\sum_{d=1}^{N}d*\sum_{i=1}^{\lfloor\frac{N}{ ...

  2. 论文笔记(9):Multiscale Combinatorial Grouping

    本文大致脉络: 对每张图片,作者首先使用 P. Doll´ar and C. Zitnick. Structured forests for fast edge detection. ICCV , 2 ...

  3. C++学习-7

    1.面向过程是:数据与操作分离,数据容易被意外修改 2.面向过程通过私有化的权限进行数据封装 3.类型后辍:类名 operator "" _XXXX(int data)  增加后缀 ...

  4. css学习の第三弹—盒模型的创建和使用

    一.css盒模型: 元素分类: 块状元素.内联元素(又叫行内元素)和内联块状元素. >>常用的块状元素有: <div>.<p>.<h1>...<h ...

  5. 你真的了解interface和内部类么

    java 访问控制符 private     : 只能被当前类访问 protected : 可以被同包的类和任何子类访问(包内,包外) default    : 可以被包内的任何内访问 public  ...

  6. emacs在windows下打开报错原因

    最开始实在是想不通,最开始我明明就能正常使用,后来发现不能用了,过了几天才回过神来,我路径中有中文,换了一个没有中文的路径后打开正常了.太低级的错误了嘛,却那么难发现. 这些数字就是识别不出来我的中文 ...

  7. 巧用UserAgent来解决浏览器的各种问题

    以前对UserAgent了解不是很透彻,今天发现UserAgent用处多多.比如我之前一直很喜欢用火狐浏览器,不过用了那么久发现火狐浏览器问题多多,比如有的论坛上传附件或者上传图片等按钮没有作用,并且 ...

  8. 基于I2C总线的MPU6050学习笔记

    MPU6050学习笔记 1. 简述 一直想自己做个四轴飞行器,却无从下手,终于狠下决心,拿出尘封已久的MPU6050模块,开始摸索着数据手册分析,一步一步地实现了MPU6050模块的功能,从MPU60 ...

  9. 托管ASP.NET Core应用程序到Windows服务中

    由于公司程序前置Nginx反向代理,所以在Windows中部署过程中没有采用IIS托管.Net Core应用,一直采用控制台dotnet命令直接运行.但是测试过程中,发现程序内Session一直无法覆 ...

  10. AVL树(Java实现)

    AVL树基本介绍 AVL树是一种自平衡的二叉查找树,在AVL树中任何节点的两个子树的高度差不能超过1.就是相当于在二叉搜索树的基础上,在插入和删除时进行了平衡处理. 不平衡的四种情况 LL:结构介绍 ...