bzoj 2733: [HNOI2012]永无乡

本文介绍了一个基于线段树合并算法解决的问题——永无乡之桥。问题涉及在一系列岛屿间建立桥梁,并查询与指定岛相连的各岛按重要度排序后的第k个岛。文章提供了一种解决方案,利用线段树合并模板实现,适用于初学者学习线段树合并技巧。

Description

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

Solution

初学线段树合并,此题中开一棵值域,由于此题中每一个节点在遍历时只会被合并一次,也就是说只有一次是被 \(O(log)\) 的遍历到,所以均摊复杂度 \(O(nlogn)\)

线段树合并模板:

int merge(int x,int y){
   if(!x)return y;if(!y)return x;
   t[x].ls=merge(t[x].ls,t[y].ls);
   t[x].rs=merge(t[x].rs,t[y].rs);
   t[x].sum=t[t[x].ls].sum+t[t[x].rs].sum;
   return x;
}
#include <algorithm>
#include <iostream>
#include <cstdlib>
#include <cstring>
#include <cstdio>
#include <cmath>
#define RG register
#define il inline
#define iter iterator
#define Max(a,b) ((a)>(b)?(a):(b))
#define Min(a,b) ((a)<(b)?(a):(b))
using namespace std;
const int N=100005;
struct node{
   int ls,rs,sum;
}t[N*20];
int n,m,Q,root[N],val[N],b[N],num=0,fa[N],cnt=0,totnode=0;
char s[3];int id[N];
int find(int x){return fa[x]==x?x:fa[x]=find(fa[x]);}
void ins(int &rt,int l,int r,int sa){
   if(!rt)rt=++totnode;
   if(l==r){t[rt].sum++;return ;}
   int mid=(l+r)>>1;
   if(sa<=mid)ins(t[rt].ls,l,mid,sa);
   else ins(t[rt].rs,mid+1,r,sa);
   t[rt].sum=t[t[rt].ls].sum+t[t[rt].rs].sum;
}
int query(int rt,int k,int l,int r){
   if(l==r)return l;
   int mid=(l+r)>>1;
   if(t[t[rt].ls].sum>=k)return query(t[rt].ls,k,l,mid);
   return query(t[rt].rs,k-t[t[rt].ls].sum,mid+1,r);
}
int merge(int x,int y){
   if(!x)return y;if(!y)return x;
   t[x].ls=merge(t[x].ls,t[y].ls);
   t[x].rs=merge(t[x].rs,t[y].rs);
   t[x].sum=t[t[x].ls].sum+t[t[x].rs].sum;
   return x;
}
void work()
{
   int x,y;
   scanf("%d%d",&n,&m);
   for(int i=1;i<=n;i++)scanf("%d",&val[i]),b[++num]=val[i],fa[i]=i;
   sort(b+1,b+num+1);
   for(int i=1;i<=n;i++)val[i]=lower_bound(b+1,b+num+1,val[i])-b;
   for(int i=1;i<=n;i++)ins(root[i],1,n,val[i]),id[val[i]]=i;
   for(int i=1;i<=m;i++){
      scanf("%d%d",&x,&y);
      if(find(x)!=find(y)){
         root[find(x)]=merge(root[find(x)],root[find(y)]);
         fa[find(y)]=find(x);
      }
   }
   scanf("%d",&Q);
   while(Q--){
      scanf("%s%d%d",s,&x,&y);
      if(s[0]=='Q'){
         if(t[root[find(x)]].sum<y)puts("-1");
         else printf("%d\n",id[query(root[find(x)],y,1,n)]);
      }
      else {
         root[find(x)]=merge(root[find(x)],root[find(y)]);
         fa[find(y)]=find(x);
      }
   }
}

int main(){work();return 0;}

转载于:https://www.cnblogs.com/Yuzao/p/7662003.html

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值