P3224 [HNOI2012]永无乡 题解
P3224 [HNOI2012]永无乡 题解
题意概括
有若干集合,每个集合最初包含一个值,和一个编号1~n。两个操作:合并两个集合,查询包含值x的集合中第k大值最初的集合编号。
思路
维护集合之间关系显然用并查集,但怎么处理询问,如果只是问最大值,那么显然可以用线段树把最大值存在并查集的祖先上,当然线段树也行。但这里问的是第k大。主席树?主席树是用来处理区间第k大的,而这里每棵树显然储存一整个集合(由多个小集合合并来的)的信息,我们并不关心这个集合内的区间问题,主席树便有点大材小用。所以,得出结论:用并查集和值域(权值)线段树合并。
你的线段树本来就能合并,那并查集是干嘛的呢?我们每次合并是取出x集合和y集合对应的A树和B树,并将B树的信息放到A上,就像这样:A B=>A+B B 如果是 A B=>A+B A+B或A B C(=A+B) 空间势必会炸。若不用并查集我们在查询集合y时,可能拿到的rt[y]树y的根就可能是 A B=>A+B B 中的那个B的而不是A+B的。用了并查集先把 f[y]=x 这样 rt[f[y] 就是 A+B 的根了。(不知不觉写了好多)
实现
其实实现很简单,但对于不熟悉值域线段树和线段树合并的人就不容易了。
值域线段树
先放这道题里涉及值域线段树的代码(代码总是比人话好理解):
int newt(int l,int r,int val)//建一棵只有一个值的树
{
int now=++tot;
t[now].v=1;
if(l==r)
return now;
int mid=l+r>>1;
if(mid>=val)
t[now].l=newt(l,mid,val);
else
t[now].r=newt(mid+1,r,val);
pushup(now);
return now;
}
int query(int now,int l,int r,int val)//查询
{
if(t[now].v<val||!now)return 0;
if(l==r)
return l;
int mid=(l+r)>>1;
if(val<=t[t[now].l].v)
return query(t[now].l,l,mid,val);
else
return query(t[now].r,mid+1,r,val-t[t[now].l].v);
}
详细请看这位大佬的blog。
线段树合并
int merge(int x,int y,int l,int r)
{
if(!x||!y)return x+y;
if(l==r){t[x].v=t[x].v+t[y].v;return x;}
int mid=l+r>>1;
t[x].l=merge(t[x].l,t[y].l,l,mid);
t[x].r=merge(t[x].r,t[y].r,mid+1,r);
pushup(x);
return x;
}
请看这位蒟蒻(我)的blog。
AC代码
#include<bits/stdc++.h>
using namespace std;
struct TREE
{
int v,l,r;
}t[5000005];
int n,m,p[100005],f[100005],rt[100005],id[100005],q,x,y,u,v,tot,k;
char op;
void pushup(int now)
{
t[now].v=t[t[now].l].v+t[t[now].r].v;
}
int find(int now)
{
if(f[now]==now)return now;
else return f[now]=find(f[now]);
}
int newt(int l,int r,int val)
{
//cout<<l<<' '<<r<<' '<<val<<endl;
int now=++tot;
t[now].v=1;
if(l==r)
return now;
int mid=l+r>>1;
if(mid>=val)
t[now].l=newt(l,mid,val);
else
t[now].r=newt(mid+1,r,val);
pushup(now);
return now;
}
int merge(int x,int y,int l,int r)
{
//cout<<x<<' '<<y<<' '<<l<<' '<<r<<endl;
if(!x||!y)return x+y;
if(l==r){t[x].v=t[x].v+t[y].v;return x;}
int mid=l+r>>1;
t[x].l=merge(t[x].l,t[y].l,l,mid);
t[x].r=merge(t[x].r,t[y].r,mid+1,r);
pushup(x);
return x;
}
int query(int now,int l,int r,int val)
{
//cout<<now<<' '<<l<<' '<<r<<endl;
if(t[now].v<val||!now)return 0;
if(l==r)
return l;
int mid=(l+r)>>1;
if(val<=t[t[now].l].v)
return query(t[now].l,l,mid,val);
else
return query(t[now].r,mid+1,r,val-t[t[now].l].v);
}
void pp()
{
for(int i=1;i<=tot;i++)cout<<setw(2)<<i<<' ';cout<<endl;
for(int i=1;i<=tot;i++)cout<<setw(2)<<f[i]<<' ';cout<<endl;
for(int i=1;i<=tot;i++)cout<<setw(2)<<rt[i]<<' ';cout<<endl;
cout<<endl;
for(int i=1;i<=tot;i++)cout<<setw(2)<<t[i].v<<' ';cout<<endl;
for(int i=1;i<=tot;i++)cout<<setw(2)<<t[i].l<<' ';cout<<endl;
for(int i=1;i<=tot;i++)cout<<setw(2)<<t[i].r<<' ';cout<<endl;
cout<<endl;
}
int main()
{
cin>>n>>m;
for(int i=1;i<=n;i++)
cin>>p[i],rt[i]=newt(1,n,p[i]),f[i]=i,id[p[i]]=i;
id[0]=-1;
//pp();
for(int i=1;i<=m;i++)
{
cin>>u>>v;
if(find(u)==find(v))continue;
rt[find(u)]=merge(rt[find(u)],rt[find(v)],1,n);
f[find(v)]=find(u);
}
cin>>q;
while(q--)
{
//
cin>>op;
if(op=='Q')
{
cin>>x>>y;
cout<<id[query(rt[find(x)],1,n,y)]<<endl;
}
if(op=='B')
{
cin>>u>>v;
if(find(u)!=find(v))
rt[find(u)]=merge(rt[find(u)],rt[find(v)],1,n),f[find(v)]=find(u);
}
} return 0;
}
后记
后来发现这道题还有更优的平衡树解法,但这个做法应该是码量最少的了,这也说明了值域线段树可以实现一些普通平衡树的操作,但不能完全替代。
P3224 [HNOI2012]永无乡 题解的更多相关文章
- 线段树合并+并查集 || BZOJ 2733: [HNOI2012]永无乡 || Luogu P3224 [HNOI2012]永无乡
题面:P3224 [HNOI2012]永无乡 题解: 随便写写 代码: #include<cstdio> #include<cstring> #include<iostr ...
- 洛谷 P3224 [HNOI2012]永无乡 解题报告
P3224 [HNOI2012]永无乡 题目描述 永无乡包含 \(n\) 座岛,编号从 \(1\) 到 \(n\) ,每座岛都有自己的独一无二的重要度,按照重要度可以将这 \(n\) 座岛排名,名次用 ...
- bzoj2733 / P3224 [HNOI2012]永无乡(并查集+线段树合并)
[HNOI2012]永无乡 每个联通块的点集用动态开点线段树维护 并查集维护图 合并时把线段树也合并就好了. #include<iostream> #include<cstdio&g ...
- 洛谷P3224 [HNOI2012]永无乡(线段树合并+并查集)
题目描述 永无乡包含 nnn 座岛,编号从 111 到 nnn ,每座岛都有自己的独一无二的重要度,按照重要度可以将这 nnn 座岛排名,名次用 111 到 nnn 来表示.某些岛之间由巨大的桥连接, ...
- 洛谷 P3224 [HNOI2012]永无乡
题面 永无乡包含 \(n\) 座岛,编号从 \(1\) 到 \(n\) ,每座岛都有自己的独一无二的重要度,按照重要度可以将这 \(n\) 座岛排名,名次用 \(1\) 到 \(n\) 来表示.某些岛 ...
- P3224 [HNOI2012]永无乡(平衡树合并)
题目描述 永无乡包含 nn 座岛,编号从 11 到 nn ,每座岛都有自己的独一无二的重要度,按照重要度可以将这 nn 座岛排名,名次用 11 到 nn 来表示.某些岛之间由巨大的桥连接,通过桥可以从 ...
- [洛谷P3224][HNOI2012]永无乡
题目大意:给你$n$个点,每个点有权值$k$,现有两种操作: 1. $B\;x\;y:$将$x,y$所在联通块合并2. $Q\;x\;k:$查询第$x$个点所在联通块权值第$k$小是哪个数 题解:线段 ...
- P3224 [HNOI2012]永无乡
思路 平衡树+启发式合并 貌似也可以线段树合并 连边就是合并两个Treap,查询就是第k大 使用Treap,好写好调 代码 #include <cstdio> #include <a ...
- 2018.08.11 洛谷P3224 [HNOI2012]永无乡(线段树合并)
传送门 给出n个带点权的点,支持连边和查询连通块第k大. 这个貌似就是一道线段树合并的裸板啊... 代码: #include<bits/stdc++.h> #define N 100005 ...
随机推荐
- 重学c#系列——string.empty 和 "" 还有null[二十]
前言 简单整理一下string.empty 和 "" 还有 null的区别. 正文 首先null 和 string.empty 还有 "" 是不一样的. nul ...
- 【LeetCode】966. Vowel Spellchecker 解题报告(Python)
作者: 负雪明烛 id: fuxuemingzhu 个人博客: http://fuxuemingzhu.cn/ 目录 题目描述 题目大意 解题方法 字典 日期 题目地址:https://leetcod ...
- 【剑指Offer】链表中环的入口结点 解题报告(Python)
[剑指Offer]链表中环的入口结点 解题报告(Python) 标签(空格分隔): 剑指Offer 题目地址:https://www.nowcoder.com/ta/coding-interviews ...
- mac学习Python第一天:安装、软件说明、运行python的三种方法
一.Python安装 从Python官网下载Python 3.x的安装程序,下载后双击运行并安装即可: Python有两个版本,一个是2.x版,一个是3.x版,这两个版本是不兼容的. MAC 系统一般 ...
- 《Head First设计模式》读书笔记
前言:本文是记录我在阅读<Head First设计模式>这本书时,做得相关笔记,相关示例代码地址:design-patterns.由于本书不是将设计原则和设计模式分开讲述的,而是在讲一个设 ...
- uniapp医院预约挂号微信小程序
开头感言:最近看小程序很火,也想弄一个看看,用了一些时间从0开始写,也记录了一些笔记,自己用框架写的模板,不是很精美,后面会慢慢优化,功能也是后面慢慢加上去的, 其中功能这块,起初只是一些简单的功能, ...
- MySql各事务隔离级别及锁问题
聊事务隔离级别和锁问题之前首先得理解事务的隔离级别和共享锁及独占锁的概念: 事务的隔离级别: 脏读 不可重复读 幻读 Read uncommitted √ √ √ Read committed × ...
- 代码质量管理sonarqube部署使用
一.sonarqube的部署 1.下载sonaqube:https://www.sonarqube.org/downloads/ 根据需要下载特定版本: 2.如果通过sonar-scanner进行代码 ...
- [c++]对vector<T>容器求交集,并集,去重
#include "iostream" #include "vector" #include "algorithm" //sort函数.交并 ...
- [opencv]KAZE、AKAZE特征检测、匹配与对象查找
AkAZE是KAZE的加速版 与SIFT,SUFR比较: 1.更加稳定 2.非线性尺度空间 3.AKAZE速度更加快 4.比较新的算法,只有Opencv新的版本才可以用 AKAZE局部匹配介绍 1.A ...