P3224 [HNOI2012]永无乡

P3224 [HNOI2012]永无乡

题目描述
永无乡包含 n 座岛,编号从 1 到 n ,每座岛都有自己的独一无二的重要度,按照重要度可以将这 n 座岛排名,名次用 1 到 n 来表示。某些岛之间由巨大的桥连接,通过桥可以从一个岛到达另一个岛。如果从岛 a 出发经过若干座(含 0 座)桥可以 到达岛 b ,则称岛 a 和岛 b 是连通的。

现在有两种操作:

B x y 表示在岛 x 与岛 y 之间修建一座新桥。

Q x k 表示询问当前与岛 x 连通的所有岛中第 k 重要的是哪座岛,即所有与岛 x 连通的岛中重要度排名第 k 小的岛是哪座,请你输出那个岛的编号。

输入输出格式
输入格式:
第一行是用空格隔开的两个正整数 n 和 m ,分别表示岛的个数以及一开始存在的桥数。

接下来的一行是用空格隔开的 n 个数,依次描述从岛 1 到岛 n 的重要度排名。随后的 m 行每行是用空格隔开的两个正整数 a_i和 b_i,表示一开始就存在一座连接岛 a_i和岛 b_i 的桥。

后面剩下的部分描述操作,该部分的第一行是一个正整数 q ,表示一共有 q 个操作,接下来的 q 行依次描述每个操作,操作的 格式如上所述,以大写字母 Q 或 B 开始,后面跟两个不超过 n 的正整数,字母与数字以及两个数字之间用空格隔开。

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


\(Treap\) 的启发式合并。

什么是启发式合并呢? 说得很高大上, 其实就是把 \(size\) 小的往 \(size\) 大的里丢。。

这里提供两种方案, 第一种, 遍历 \(size\) 小的 \(Treap\), 将其节点作为一个新节点, 重新加入大的树, 也就是说, 丢弃之前的节点, 重新插入。 最坏的情况,每次以 \(2\) 倍合并(像 \(2048\) 那样), 最坏情况空间为 \(2N\)

我采用的是将节点再加入新树的方式。 合并的时候直接用已有的编号, 等于回收了空间, 空间占用较小可是略显复杂

如何遍历一颗 \(Treap\)

采用 \(dfs\) 的方式。每个点只有两个儿子, \(dfs\) 很好写。不过有些细节需要注意, 我将节点新的初始化写在了 \(dfs\) 里, 比如在加入新树之前要将此节点的儿子清空(因为重要度不重复, 加入新树后必定是叶子节点), 防止出现一些 玄学 错误

void add(int &id, int p, int v){
    if(!id){id = p;pushup(id);return ;}
    int d = v < val[id] ? 0 : 1;
    add(ch[id][d], p, v);
    if(dat[ch[id][d]] > dat[id])Rotate(id, d ^ 1);
    pushup(id);
    }

void dfs(int id, int &root){//注意引用来修改入口的根节点
    int v1 = ch[id][0], v2 = ch[id][1];
    ch[id][0] = ch[id][1] = 0;
    size[id] = 1;
    add(root, id, val[id]);
    if(v1)dfs(v1, root);
    if(v2)dfs(v2, root);
    }

如何开很多个 \(Treap\) 而不爆空间

且维护节点之间的关系?

维护节点之间的关系使用并查集即可。

刚开始想的是开 \(N\)满大小的 \(Treap\) ,发现肯定会爆空间, 于是乎开每个点即可, 这里的 \(ch[maxn][2]\) 代表的就不是一颗 \(Treap\) 了, 而是一片森林中父亲与儿子的关系。 因为我们可以回收空间, \(ch\) 数组大小 开一倍即可。 因为有多颗树, 我们开一个 \(root[\ ]\) 表示某个节点的根节点, 考虑使用并查集维护即可。

int father[maxn];
int findfather(int v){
    if(father[v] == v)return v;
    return father[v] = findfather(father[v]);
    }
    
//---------------------------------------------------------------------
    
int num, ne, na;
void dfs(int id, int &root){
    int v1 = ch[id][0], v2 = ch[id][1];
    ch[id][0] = ch[id][1] = 0;
    size[id] = 1;
    add(root, id, val[id]);
    if(v1)dfs(v1, root);
    if(v2)dfs(v2, root);
    }   
void merge(int a, int b){
    int faA = findfather(a), faB = findfather(b);
    if(faA == faB)return ;//把A加入B
    if(size[root[faA]] > size[root[faB]])swap(faA, faB);
    dfs(root[faA], root[faB]);
    father[faA] = faB;
    }

Code

#include<iostream>
#include<cstdio>
#include<queue>
#include<cstring>
#include<algorithm>
using namespace std;
int RD(){
    int flag = 1, out = 0;char c = getchar();
    while(c < '0' || c > '9'){if(c == '-')flag = -1;c = getchar();}
    while(c >= '0' && c <= '9'){out = out * 10 + c - '0';c = getchar();}
    return flag * out;
    }
const int maxn = 1000019, INF = 1e9 + 19;
int ch[maxn][2];
int size[maxn];
int val[maxn], dat[maxn];
int root[maxn], tot;
int New(int v){
    val[++tot] = v;dat[tot] = rand();
    size[tot] = 1;
    return tot;
    }
void pushup(int id){size[id] = size[ch[id][0]] + size[ch[id][1]] + 1;}
void Rotate(int &id, int d){
    int temp = ch[id][d ^ 1];
    ch[id][d ^ 1] = ch[temp][d];
    ch[temp][d] = id;
    id = temp;
    pushup(ch[id][d]), pushup(id);
    }

int get_val(int id, int rank){
    if(!id)return 0;
    if(size[ch[id][0]] >= rank)return get_val(ch[id][0], rank);
    else if(size[ch[id][0]] + 1 >= rank)return id;
    else return get_val(ch[id][1], rank - size[ch[id][0]] - 1);
    }
void add(int &id, int p, int v){
    if(!id){id = p;pushup(id);return ;}
    int d = v < val[id] ? 0 : 1;
    add(ch[id][d], p, v);
    if(dat[ch[id][d]] > dat[id])Rotate(id, d ^ 1);
    pushup(id);
    }

//--------------------------------------------------------------------

int father[maxn];
int findfather(int v){
    if(father[v] == v)return v;
    return father[v] = findfather(father[v]);
    }
    
//---------------------------------------------------------------------
    
int num, ne, na;
void dfs(int id, int &root){
    int v1 = ch[id][0], v2 = ch[id][1];
    ch[id][0] = ch[id][1] = 0;
    size[id] = 1;
    add(root, id, val[id]);
    if(v1)dfs(v1, root);
    if(v2)dfs(v2, root);
    }   
void merge(int a, int b){
    int faA = findfather(a), faB = findfather(b);
    if(faA == faB)return ;//把A加入B
    if(size[root[faA]] > size[root[faB]])swap(faA, faB);
    dfs(root[faA], root[faB]);
    father[faA] = faB;
    }
int main(){
    num = RD(); ne = RD();
    for(int i = 1;i <= num;i++)father[i] = i;
    for(int i = 1;i <= num;i++)root[findfather(i)] = New(RD());
    for(int i = 1;i <= ne;i++){
        int u = RD(), v = RD();
        merge(u, v);
        }
    na = RD();
    char cmd;
    for(int i = 1;i <= na;i++){
        cin>>cmd;
        if(cmd == 'Q'){
            int x = RD(), k = RD();
            int rt = root[findfather(x)];
            if(size[rt] < k)printf("-1\n");
            else printf("%d\n", get_val(rt, k));
            }
        else{
            int a = RD(), b = RD();
            merge(a, b);
            }
        }
    return 0;
    }

转载于:https://www.cnblogs.com/Tony-Double-Sky/p/9327195.html

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
毕业设计,基于SpringBoot+Vue+MySQL开发的影城管理系统,源码+数据库+论文答辩+毕业论文+视频演示 随着现在网络的快速发展,网上管理系统也逐渐快速发展起来,网上管理模式很快融入到了许多生活之中,随之就产生了“小徐影城管理系统”,这样就让小徐影城管理系统更加方便简单。 对于本小徐影城管理系统的设计来说,系统开发主要是采用java语言技术,在整个系统的设计中应用MySQL数据库来完成数据存储,具体根据小徐影城管理系统的现状来进行开发的,具体根据现实的需求来实现小徐影城管理系统网络化的管理,各类信息有序地进行存储,进入小徐影城管理系统页面之后,方可开始操作主控界面,主要功能包括管理员:首页、个人中心、用户管理、电影类型管理、放映厅管理、电影信息管理、购票统计管理、系统管理、订单管理,用户前台;首页、电影信息、电影资讯、个人中心、后台管理、在线客服等功能。 本论文主要讲述了小徐影城管理系统开发背景,该系统它主要是对需求分析和功能需求做了介绍,并且对系统做了详细的测试和总结。具体从业务流程、数据库设计和系统结构等多方面的问题。望能利用先进的计算机技术和网络技术来改变目前的小徐影城管理系统状况,提高管理效率。 关键词:小徐影城管理系统;Spring Boot框架,MySQL数据库
根据引用\[1\]和引用\[2\]的描述,题目中的影魔拥有n个灵魂,每个灵魂有一个战斗力ki。对于任意一对灵魂对i,j (i<j),如果不存在ks (i<s<j)大于ki或者kj,则会为影魔提供p1的攻击力。另一种情况是,如果存在一个位置k,满足ki<c<kj或者kj<c<ki,则会为影魔提供p2的攻击力。其他情况下的灵魂对不会为影魔提供攻击力。 根据引用\[3\]的描述,我们可以从左到右进行枚举。对于情况1,当扫到r\[i\]时,更新l\[i\]的贡献。对于情况2.1,当扫到l\[i\]时,更新区间\[i+1,r\[i\]-1\]的贡献。对于情况2.2,当扫到r\[i\]时,更新区间\[l\[i\]+1,i-1\]的贡献。 因此,对于给定的区间\[l,r\],我们可以根据上述方法计算出区间内所有下标二元组i,j (l<=i<j<=r)的贡献之和。 #### 引用[.reference_title] - *1* *3* [P3722 [AH2017/HNOI2017]影魔(树状数组)](https://blog.csdn.net/li_wen_zhuo/article/details/115446022)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v91^insertT0,239^v3^insert_chatgpt"}} ] [.reference_item] - *2* [洛谷3722 AH2017/HNOI2017 影魔 线段树 单调栈](https://blog.csdn.net/forever_shi/article/details/119649910)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v91^insertT0,239^v3^insert_chatgpt"}} ] [.reference_item] [ .reference_list ]
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值