【并查集套可持久化线段树】【bzoj 2733】: [HNOI2012]永无乡

本博客详细解析了HNOI2012竞赛中的永无乡问题,涉及并查集、线段树等数据结构应用,以及操作指令的处理逻辑。通过实例分析,深入讲解了如何使用并查集进行集合合并,并利用线段树高效查询特定条件下的集合元素。适合竞赛准备和算法学习。


2733: [HNOI2012]永无乡

Time Limit: 10 Sec   Memory Limit: 128 MB
Submit: 1178   Solved: 614
[ Submit][ Status]

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

输入文件第一行是用空格隔开的两个正整数 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

对于每个 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

题意:
n个独立点有各不相同的权值
开始合并m次,每次合并两个点所在集合
两种操作
Q x y 询问x所在集合的权值第y大的点
B x y 合并x、y的所在集合


输入:
n,m
n个点,m条初始边
n个数,每个点的权值
m行
每行a、b
合并a、b所在集合
接下来一个q
q行
Q x y 询问x所在集合的权值第y大的点
B x y 合并x、y的所在集合


线段树+并查集
代码画个图,也挺容易理解的 
 
这是自己YY hzwer 神犇的代码,考试的时候花半个多小时默写了一遍 
但是不知复杂度如何计算,总觉得merge的复杂度大于O(logn) 
还有不知道数组应该开多大,我觉得要开nlogn ,算出来比实际值小了一点


考完发现过了样例就交,竟然1A了。结果考试完挂。。。


代码比hzwer慢了一点,不知为什么


#include <cstdio>
#include <cstring>
#include <iostream>
#include <cmath>
#include <cstdlib>
#include <algorithm>
using namespace std;
/************************************************
Code By willinglive

线段树+并查集
代码画个图,也挺容易理解的 
 
这是自己YY hzwer 神犇的代码,考试的时候花半个多小时默写了一遍 
但是不知复杂度如何计算,总觉得merge的复杂度大于O(logn) 
还有不知道数组应该开多大,我觉得要开nlogn 

Oct.30,2014
************************************************/
/////////////////////////////////////////////////
#define rep(i,l,r) for(int i=l,___t=(r);i<=___t;i++)
#define per(i,r,l) for(int i=r,___t=(l);i>=___t;i--)
#define LL long long
#define MS(arr,x) memset(arr,x,sizeof(arr))
#define INE(i,u,e) for(int i=head[u];~i;i=e[i].next)
/////////////////////////////////////////////////
const int N=100010;
int n,m,q;

int fa[N];
int rank[N];

int root[N],ls[1800000],rs[1800000];
int sum[1800000],sz;
int val[1800000];

int id[N];//权值 -> 点 
/////////////////////////////////////////////////
int find(int x){return x==fa[x]?x:fa[x]=find(fa[x]);}
inline int getint()
{
	int res=0;char c=getchar();
	while(!isdigit(c))c=getchar();
	while(isdigit(c))res=res*10+c-'0',c=getchar();
	return res;
}

void insert(int &o,int pos,int l,int r)
{
	if(!o) o=++sz;
	if(l==r) {val[o]=pos;sum[o]=1;return;}
	int mid=(l+r)>>1;
	if(pos<=mid) insert(ls[o],pos,l,mid);
	else	     insert(rs[o],pos,mid+1,r);
	sum[o]=sum[ls[o]]+sum[rs[o]];
}
int query(int o,int pos)
{
	if(sum[o]==1)
	{
		if(ls[o]) return query(ls[o],pos);
		else if(rs[o]) return query(rs[o],pos);
		return val[o];
	}
	if(sum[ls[o]]>=pos) return query(ls[o],pos);
	else return query(rs[o],pos-sum[ls[o]]);
}
void merge(int &x,int &y)
{
	if(x==0){x=y;return;}
	if(y==0){y=x;return;}
	merge(ls[x],ls[y]);
	merge(rs[x],rs[y]);
	sum[x]=sum[ls[x]]+sum[rs[x]];
	sum[y]=sum[ls[y]]+sum[rs[y]];
}
/////////////////////////////////////////////////
void input()
{
    n=getint(); m=getint();
    rep(i,1,n) rank[i]=getint(),fa[i]=i,id[rank[i]]=i;
    rep(i,1,m) fa[find(getint())]=find(getint());
    q=getint();
}
void solve()
{
    /////////////////////////////////////
    char op[5];
    int x,y;
    /////////////////////////////////////
    rep(i,1,n) insert(root[find(i)],rank[i],1,n);
    
    while(q--)
    {
    	scanf("%s",op);
    	x=getint(); y=getint();
    	if(op[0]=='Q')
    	{
    		int fx=find(x);
    		if(sum[root[fx]]<y){puts("-1");continue;}
    		printf("%d\n",id[query(root[fx],y)]);
		}
		else
		{
			int fx=find(x);
			int fy=find(y);
			if(fx!=fy)
			{
    			fa[fy]=fx;
    			merge(root[fx],root[fy]);
		    }
		}
	}
    /////////////////////////////////////
}
int main()
{
    freopen("std.in","r",stdin);
    freopen("std.out","w",stdout);
    input();
    solve();
    return 0;
}



评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值