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));
            }
         }
    }
    
  • 相关阅读:
    Windows server 2016 解决“无法完成域加入,原因是试图加入的域的SID与本计算机的SID相同。”
    Windows Server 2016 辅助域控制器搭建
    Windows Server 2016 主域控制器搭建
    Net Framework 4.7.2 覆盖 Net Framework 4.5 解决办法
    SQL SERVER 2012更改默认的端口号为1772
    Windows下彻底卸载删除SQL Serever2012
    在Windows Server2016中安装SQL Server2016
    SQL Server 创建索引
    C#控制台或应用程序中两个多个Main()方法的设置
    Icon cache rebuilding with Delphi(Delphi 清除Windows 图标缓存源代码)
  • 原文地址:https://www.cnblogs.com/Xing-Ling/p/10803398.html
Copyright © 2011-2022 走看看