zoukankan      html  css  js  c++  java
  • 【题解】永无乡 [HNOI2012] [BZOJ2733] [P3224]

    【题解】永无乡 [HNOI2012] [BZOJ2733] [P3224]


    【题目描述】

    永无乡包含 (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) 个操作。

    【输出】

    对于每个 (Q) (x) (k) 的操作依次输出一行,其中包含一个整数,表示所询问岛屿的编号。如果该岛屿不存在,则输出 (-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
    2
    5
    1
    2
    

    【数据范围】

    (20\%) (1 leqslant n leqslant 1000,1 leqslant q leqslant 1000)

    (100\%) (1 leqslant n leqslant 1e5,m leqslant n,1 leqslant q leqslant 3e5)


    【分析】

    不会线段树的先到隔壁去逛逛:线段树详解(全)

    一道线段树合并好题

    并查集维护各个连通块,每个块都建立一棵权值线段树,在合并两个块的同时,将它们的线段树也进行合并。那么原问题就变成了在一棵线段树中求第 (k) 小,而这个是权值线段树的基本操作。

    【Code】

    #include<algorithm>
    #include<cstdio>
    #define mid (L+R>>1)
    #define pl tr[p].lp
    #define pr tr[p].rp
    #define Re register int
    #define F(a,b) for(i=a;i<=b;++i)
    using namespace std;
    const int N=2e5+3;char c;
    int x,y,i,n,m,cnt,f[N],id[N],pt[N];//pt[i]表示离散化后i这个位置所对应的权值树根的编号 
    struct QAQ{int g,lp,rp;}tr[N<<5];//权值树,保守开一个32*N
    inline void in(Re &x){//【快读】自己动手,丰衣足食... 
        x=0;char c=getchar();
        while(c<'0'||c>'9')c=getchar();
        while(c>='0'&&c<='9')x=(x<<1)+(x<<3)+(c^48),c=getchar();
    }
    inline int build(Re L,Re R,Re x){//开一个空的权值树
        Re p=++cnt;++tr[p].g;//【动态开点】  
        if(L==R)return p;
        if(x<=mid)pl=build(L,mid,x);
        else pr=build(mid+1,R,x);
        return p;
    }
    inline int merge(Re p,Re q){//【线段树合并】 
        if(!p)return q;if(!q)return p;
        //当需要合并的点的其中一个编号为0时 (即为空),返回另一个编号 
        tr[p].g+=tr[q].g,p;//把q合并到p上面去 
        pl=merge(pl,tr[q].lp);//分别合并左子树,右子树 
        pr=merge(pr,tr[q].rp);
        return p;
    }
    inline int ask(Re p,Re L,Re R,Re k){//查询
        if(L==R)return id[R];//边界:L==R
        Re tmp=tr[pl].g;//计算左子树共有多少个数字 
        if(tmp>=k)return ask(pl,L,mid,k);//左子树已经超过k个,说明第k小在左子树里面 
        else return ask(pr,mid+1,R,k-tmp);//左子树不足k个,应该在右子树中找第(k-tmp)小 
    }
    inline int find(Re x){if(x!=f[x])f[x]=find(f[x]);return f[x];}
    int main(){
        in(n),in(m); 
        F(1,n)in(x),id[x]=i,f[i]=i,pt[i]=build(1,n,x);
        //用id[x]表示重要度为x的点的编号,给每个点建一棵树 
        while(m--){//初始的桥要连起来 
        	in(x),in(y),x=find(x),y=find(y);
        	merge(pt[x],pt[y]),f[y]=f[x];//注意这里merge(pt[],pt[])和f[]=f[]中x,y的顺序要相反 
        }
        in(m);
        while(m--){
            scanf(" %c",&c),in(x),in(y),x=find(x);
            if(c=='B')y=find(y),merge(pt[x],pt[y]),f[y]=f[x];
            else{
                if(tr[pt[x]].g<y)printf("-1
    ");//如果总个数都小于y,说明没有第y大的数,直接输出-1 
                else printf("%d
    ",ask(pt[x],1,n,y));
            }
         }
    }
    
  • 相关阅读:
    Maven 打包指定子工程项目(springcloud分模块打包)
    linux常见问题: zip/unzip: command not found
    CentOS8安装jdk1.8
    nacos-docker镜像安装nacos并配置数据库
    浏览器的一个请求从发送到返回都经历了什么
    python之scrapy
    常见的爬虫与反爬虫斗争
    Python闭包与延迟绑定
    ip代理
    python编程:统计文件中单词出现次数
  • 原文地址:https://www.cnblogs.com/Xing-Ling/p/10803398.html
Copyright © 2011-2022 走看看