洛谷 P3224 [HNOI2012] 永无乡(启发式合并+splay)

题目描述

永无乡包含 𝑛 座岛,编号从 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;
}

根据引用\[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、付费专栏及课程。

余额充值