题目描述
永无乡包含 𝑛 座岛,编号从 1 到 𝑛 ,每座岛都有自己的独一无二的重要度,按照重要度可以将这 𝑛 座岛排名,名次用 1 到 𝑛 来表示。某些岛之间由巨大的桥连接,通过桥可以从一个岛到达另一个岛。如果从岛 𝑎 出发经过若干座(含 0 座)桥可以 到达岛 𝑏,则称岛 𝑎 和岛 𝑏 是连通的。
现在有两种操作:
B x y
表示在岛 𝑥 与岛 𝑦 之间修建一座新桥。
Q x k
表示询问当前与岛 𝑥 连通的所有岛中第 𝑘 重要的是哪座岛,即所有与岛 𝑥 连通的岛中重要度排名第 𝑘 小的岛是哪座,请你输出那个岛的编号。
输入格式
第一行是用空格隔开的两个整数,分别表示岛的个数 𝑛 以及一开始存在的桥数 𝑚。
第二行有 𝑛 个整数,第 𝑖个整数表示编号为 𝑖 的岛屿的排名 𝑝𝑖。
接下来 𝑚 行,每行两个整数 𝑢,𝑣,表示一开始存在一座连接编号为 𝑢 的岛屿和编号为 𝑣 的岛屿的桥。
接下来一行有一个整数,表示操作个数 𝑞。
接下来 𝑞 行,每行描述一个操作。每行首先有一个字符 𝑜𝑝,表示操作类型,然后有两个整数 𝑥,𝑦。
- 若 𝑜𝑝 为
Q
,则表示询问所有与岛 𝑥 连通的岛中重要度排名第 𝑦 小的岛是哪座,请你输出那个岛的编号。 - 若 𝑜𝑝 为
B
,则表示在岛 𝑥 与岛 𝑦 之间修建一座新桥。
输出格式
对于每个询问操作都要依次输出一行一个整数,表示所询问岛屿的编号。如果该岛屿不存在,则输出 −1 。
输入输出样例
输入 #1复制
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
输出 #1复制
-1 2 5 1 2
说明/提示
数据规模与约定
- 对于 20% 的数据,保证 𝑛≤1e3, 𝑞≤1e3。
- 对于 100% 的数据,保证 1≤𝑚≤𝑛≤1e5, 1≤𝑞≤3×1e5,𝑝 为一个 1∼𝑛 的排列,𝑜𝑝∈{Q,B},1≤𝑢,𝑣,𝑥,𝑦≤𝑛。
思路:
用并查集维护集合间的关系,用splay维护每个集合,当两个集合合并的时候,将集合中节点少的一个集合暴力合并到集合中节点多的一棵splay上,然后查询的时候就是简单的splay查询。
注意,本题看多少个节点很重要,在洛谷上开了1e6才过了(我也不会计算)
时间复杂度O(nlognlogn)
AC代码如下:
//2024-7-9
#include<bits/stdc++.h>
using namespace std;
const int N=1000010;//这里要开1e6,不然在洛谷上过不了
int n,m;
//splay维护的是按照价值排序
struct Node
{
int s[2],p,v,id;
int size;
void init(int _v,int _id,int _p)
{
v=_v;
id=_id;
p=_p;
size=1;
}
}tr[N];
int root[N],idx;
int p[N];
int find(int x)
{
if(p[x]!=x)
p[x]=find(p[x]);
return p[x];
}
void pushup(int x)
{
tr[x].size=tr[tr[x].s[0]].size+tr[tr[x].s[1]].size+1;
}
void rotate(int x)
{
int y=tr[x].p;
int z=tr[y].p;
int k=tr[y].s[1]==x;
tr[z].s[tr[z].s[1]==y]=x;
tr[x].p=z;
tr[y].s[k]=tr[x].s[k^1];
tr[tr[x].s[k^1]].p=y;
tr[x].s[k^1]=y;
tr[y].p=x;
pushup(y);
pushup(x);
}
void splay(int x,int k,int b)//将b为根的树中的x节点旋转到k节点下面
{
while(tr[x].p!=k)
{
int y=tr[x].p;
int z=tr[y].p;
if(z!=k)
{
if((tr[y].s[1]==x)^(tr[z].s[1]==y))//之字型
rotate(x);
else//一字型
rotate(y);
}
rotate(x);
}
if(!k)
root[b]=x;//保证splay的时间复杂度
}
void insert(int v,int id,int b)//将价值为v,编号为id的节点添加到以b为根的树中
{
int u=root[b],p=0;
while(u)
{
p=u;
u=tr[u].s[v>tr[u].v];
}
u=++idx;
if(p)
tr[p].s[v>tr[p].v]=u;
tr[u].init(v,id,p);
splay(u,0,b);
}
int get_k(int k,int b)//在以b为根的树中找中序遍历的第k个节点的id
{
int u=root[b];
while(u)
{
if(tr[tr[u].s[0]].size>=k)
u=tr[u].s[0];
else if(tr[tr[u].s[0]].size+1==k)
return tr[u].id;
else
{
k-=tr[tr[u].s[0]].size+1;
u=tr[u].s[1];
}
}
return -1;
}
void dfs(int u,int b)//将u树中的所有节点复制一份添加到以b为根的树中
{
if(tr[u].s[0])
dfs(tr[u].s[0],b);
if(tr[u].s[1])
dfs(tr[u].s[1],b);
insert(tr[u].v,tr[u].id,b);
}
int main()
{
scanf("%d%d",&n,&m);
for(int i=1;i<=n;i++)
{
p[i]=root[i]=i;//第i个集合的根节点为i
int v;
scanf("%d",&v);
tr[i].init(v,i,0);
}
idx=n;
while(m--)
{
int a,b;
scanf("%d%d",&a,&b);
a=find(a),b=find(b);
if(a!=b)
{
if(tr[root[a]].size>tr[root[b]].size)//启发式合并,将节点少的splay合并到节点多的splay中
swap(a,b);
dfs(root[a],b);//将a集合中的每个点加入到b集合当中
p[a]=b;
}
}
scanf("%d",&m);
while(m--)
{
char op[2];
int a,b;
scanf("%s%d%d",op,&a,&b);
if(op[0]=='B')
{
a=find(a),b=find(b);
if(a!=b)
{
if(tr[root[a]].size>tr[root[b]].size)
swap(a,b);
dfs(root[a],b);
p[a]=b;
}
}
else
{
a=find(a);
if(tr[root[a]].size<b)
puts("-1");
else
printf("%d\n",get_k(b,a));
}
}
return 0;
}