• HNOI 2012 永无乡


    codevs 1477 永无乡

    http://codevs.cn/problem/1477/

    2012年湖南湖北省队选拔赛

     时间限制: 1 s
     空间限制: 128000 KB
     
    题目描述 Description

    永无乡包含 n 座岛,编号从 1 到 n,每座岛都有自己的独一无二的重要度,按照重要度可
    以将这 n 座岛排名,名次用 1 到 n 来表示。某些岛之间由巨大的桥连接,通过桥可以从一个岛
    到达另一个岛。如果从岛 a 出发经过若干座(含 0 座)桥可以到达岛 b,则称岛 a 和岛 b 是连
    通的。现在有两种操作:B x y 表示在岛 x 与岛 y 之间修建一座新桥。Q x k 表示询问当前与岛
    x 连通的所有岛中第 k 重要的是哪座岛,即所有与岛 x 连通的岛中重要度排名第 k 小的岛是哪
    座,请你输出那个岛的编号。

    输入描述 Input Description

    从文件 input.txt 中读入数据,输入文件第一行是用空格隔开的两个正整数 n 和 m,分别
    表示岛的个数以及一开始存在的桥数。接下来的一行是用空格隔开的 n 个数,依次描述从岛 1
    到岛 n 的重要度排名。随后的 m 行每行是用空格隔开的两个正整数 ai 和 bi,表示一开始就存
    在一座连接岛 ai 和岛 bi 的桥。后面剩下的部分描述操作,该部分的第一行是一个正整数 q,
    表示一共有 q 个操作,接下来的 q 行依次描述每个操作,操作的格式如上所述,以大写字母 Q
    或 B 开始,后面跟两个不超过 n 的正整数,字母与数字以及两个数字之间用空格隔开。
    对于 20%的数据 n≤1000,q≤1000
    对于 100%的数据 n≤100000,m≤n,q≤300000

    输出描述 Output Description

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

    样例输入 Sample Input

    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

    样例输出 Sample Output

    -1

    2

    5

    1

    2

    本题卡cin啊啊啊啊啊啊啊啊啊!!!!!!

    法一:启发式合并+并查集+splay

    #include<cstdio>
    #include<iostream>
    #include<algorithm>
    #define N 100100
    using namespace std;
    int n,m,p;
    int key[N],tot,siz[N],ch[N][2],fa[N],F[N];
    int find(int x) {return x==F[x] ? F[x] : F[x]=find(F[x]);}
    int read()
    {
        int x=0;char c=getchar();
        while(c<'0'||c>'9') c=getchar();
        while(c>='0'&&c<='9') {x=x*10+c-'0';c=getchar();}
        return x;
    }
    int getson(int x)
    {
        return ch[fa[x]][1]==x;
    }
    void update(int x)
    {
        if(!x) return;
        siz[x]=siz[ch[x][0]]+siz[ch[x][1]]+1;
    }
    void rotate(int x)
    {
        int y=fa[x],z=fa[y],kind=getson(x);
        ch[y][kind]=ch[x][kind^1];ch[x][kind^1]=y;
        fa[ch[y][kind]]=y;fa[y]=x;
        fa[x]=z;
        if(z) ch[z][ch[z][1]==y]=x;
        update(y);
    }
    void splay(int x)
    {
        for(int f;f=fa[x];rotate(x))
         if(fa[f]) rotate(getson(x)==getson(f) ? f:x);
        update(x);
    }
    void link(int x,int y)
    {
        int now=x;
        while(ch[now][key[y]>key[now]]) now=ch[now][key[y]>key[now]];
        fa[y]=now;
        ch[now][key[y]>key[now]]=y;
        siz[y]=1;
        update(now);
        splay(y);
    }
    void merge(int now,int y)
    {
        int tmp1=0,tmp2=0;
        if(ch[y][0]) tmp1=ch[y][0],ch[y][0]=0;
        if(ch[y][1]) tmp2=ch[y][1],ch[y][1]=0;
        link(now,y);
        if(tmp1) merge(y,tmp1);
        if(tmp2) merge(y,tmp2);
    }
    int query(int x,int k)
    {
        splay(x);
        if(k>siz[x]) return -1;
        int now=x;
        while(1)
        {
            int tmp=ch[now][0] ? siz[ch[now][0]] : 0;
            if(k<=tmp) now=ch[now][0];
            else 
            {
                if(k==tmp+1) return now;
                else 
                {
                    now=ch[now][1];
                    k-=tmp+1;
                } 
            }
        }
    }
    int main()
    {
        n=read();m=read();
        for(int i=1;i<=n;i++)
         {
             siz[i]++;
             key[i]=read();
             F[i]=i;
         }
        int x,y,X,Y;
        for(int i=1;i<=m;i++)
        {
            x=read();y=read();
            X=find(x);Y=find(y);
            if(X!=Y)
            {
                F[X]=Y;
                splay(X);splay(Y);
                if(siz[X]<siz[Y]) swap(X,Y);
                merge(X,Y);            
            }
        }
        p=read();
        char c[3];
        for(int i=1;i<=p;i++)
        {
            scanf("%s",c);
            x=read();y=read();
            if(c[0]=='B') 
            {
                X=find(x);Y=find(y);
                if(X!=Y)
                {
                    F[X]=Y;
                    splay(X);splay(Y);
                    if(siz[X]<siz[Y]) swap(X,Y);
                    merge(X,Y);
                }        
            }
            else 
            {
                int t=query(x,y);
                printf("%d
    ",t);
            }
        }
    }
    View Code

    2个错误:

    1、

    void update(int x)
    {
      if(!x) return;
      siz[x]=siz[ch[x][0]]+siz[ch[x][1]]+1;
    }

    没有判断x是否为0

    2、

    void link(int x,int y)
    {
      int now=x;
      while(ch[now][key[y]>key[now]]) now=ch[now][key[y]>key[now]];
      fa[y]=now;
      ch[now][key[y]>key[now]]=y;
      siz[y]=1;
      update(now);
      splay(y);
    }

    没有修改siz[y] 没有splay(y)

    先修改siz[y]=1,防止y本身还带有左右孩子

                         同时 update(now) 更新now 的siz

    splay(y)不是多余的操作,他的目的不是将y转到根节点

    而是更改自y至根节点所在路径上的siz,splay操作恰好可以完成

    同时为后续的merge 操作准备根节点

    法二、启发式合并+并查集+线段树

    #include<cstdio>
    #include<iostream>
    #define N 100001
    using namespace std;
    int n,m,p,key[N],tot,sa[N],fa[N],root[N];
    struct node
    {
        int l,r,siz;
    }tr[N*20];
    int find(int x) {return x==fa[x] ? fa[x] :fa[x]=find(fa[x]);}
    void add(int & k,int l,int r,int x)
    {
        if(!k) k=++tot;
        if(l==r) {tr[k].siz=1;return;}
        int mid=l+r>>1;
        if(x<=mid) add(tr[k].l,l,mid,x);
        else add(tr[k].r,mid+1,r,x);
        tr[k].siz=tr[tr[k].l].siz+tr[tr[k].r].siz;
    }
    int merge(int x,int y)
    {
        if(!x) return y;
        if(!y) return x;
        tr[x].l=merge(tr[x].l,tr[y].l);
        tr[x].r=merge(tr[x].r,tr[y].r);
        tr[x].siz=tr[tr[x].l].siz+tr[tr[x].r].siz;
        return x;
    }
    int query(int x,int l,int r,int k)
    {
        if(l==r) return l;
        int mid=l+r>>1,tmp=tr[tr[x].l].siz;
        if(tmp>=k) return query(tr[x].l,l,mid,k);
        else return query(tr[x].r,mid+1,r,k-tmp);
    }
    int read()
    {
        int x=0,f=1;char ch=getchar();
        while(ch<'0'||ch>'9'){if(ch=='-')f=-1;ch=getchar();}
        while(ch>='0'&&ch<='9') x=x*10+ch-48,ch=getchar();
        return x*f;
    }
    int main()
    {
        n=read();m=read();
        for(int i=1;i<=n;i++) key[i]=read(),fa[i]=i,sa[key[i]]=i;
        int x,y,r1,r2;
        for(int i=1;i<=m;i++)
        {
            x=read();y=read();
            r1=find(x),r2=find(y);
            fa[r1]=r2;
        }
        for(int i=1;i<=n;i++)
        {
            int r=find(i);
            add(root[r],1,n,key[i]);
        }
        p=read();
        char c[3];int s;
        for(int i=1;i<=p;i++)
        {
            scanf("%s",c);
            x=read();y=read();
            if(c[0]=='B')
            {
                r1=find(x);r2=find(y);
                if(r1!=r2)
                {
                    fa[r2]=r1;
                    merge(root[r1],root[r2]);
                }
            }
            else
            {
                s=find(x);
                if(tr[root[s]].siz<y) printf("-1
    ");
                else
                {
                    s=query(root[s],1,n,y);
                    printf("%d
    ",sa[s]);
                }
            }
        }
    }
    View Code

    联想 动态线段树 SDOI2014 旅行http://www.cnblogs.com/TheRoadToTheGold/p/6394842.html

    2个错误:

    1、合并两颗线段树

    错误代码:

    void merge(int x,int y)
    {
      if(!x&&!y) return;
      tr[x].siz+=tr[y].siz;
      merge(tr[x].l,tr[y].l);
      merge(tr[x].r,tr[y].r);
    }

    原因:当x=0,y不为0时 应把y的信息合并到x所对应的位置上,但由于x为0,y的信息合并不上

    所以正确合并:类似于主席树中的共用子节点

    int merge(int x,int y)
    {
      if(!x) return y;
      if(!y) return x;
      tr[x].l=merge(tr[x].l,tr[y].l);
      tr[x].r=merge(tr[x].r,tr[y].r);
      tr[x].siz=tr[tr[x].l].siz+tr[tr[x].r].siz;
      return x;
    }

    2、并查集与线段树合并的结合

    if(r1!=r2)
    {
      fa[r2]=r1;
      merge(root[r1],root[r2]);
    }

    并查集中i的父亲置为j,i的信息就要往j上合

    r1、r2对应顺序不能乱

    反思一:

    两种方法相对比:

    1、并查集的作用

    ①splay合并不需要考虑并查集内的父子关系,因为splay合并2棵树时,都将代表节点转至根节点

    并查集只起指示哪两个集合的作用

    ②线段树中并查集内部i的父亲指向j,线段树合并时,必须i所在线段树合并到j所在线段树

    因为合并哪两颗线段树需要root[i]指定 ,而i的确定是通过并查集找的

    如果线段树合并j合向i,但i在并查集中的父亲指向j,

    那么再来合并i和k时,合并的两棵树原来的是j和k,原来的j仍然只有j的信息

    为什么splay不需要呢?

    因为splay合并之前,先将2棵树的代表节点转至根节点

    如果j合向i,那么k无论合向j还是i,都会将j或i转至根节点,而i或j是真的在同一棵树上

    一句话解释就是 splay 中节点标号就是节点标号,线段树中对节点进行了重新标号,所以需要依靠root数组确定线段树中的节点对应题目中的哪个节点

    2、合并方式 

    ① splay 小的合并到大的上,因为合并是暴力把一颗splay上的点一个一个插到另一颗splay上,所以插得点越少越好

    ② 形态相同(权且这样认为)2颗线段树直接合并

    3、合并之后,对被合并的树的处理

    ①splay 把被合并的那一颗树的所有信息都清零,因为节点编号不变,节点到新的树上信息仍存在这个编号下

    ② 线段树不用管,因为这棵树被合并后相当于消失了,并查集的父子关系使他不可能被找到

    反思二:

    为什么不是主席树

    简单理解:主席树维护链上信息,线段树维护一坨点的信息

    联想SDOI 2013 森林 http://www.cnblogs.com/TheRoadToTheGold/p/6524904.html

    森林选用的是主席树,因为他要确定两点间路径,从这条链上找第k值

    今天一天只做了1道题,2个原因

    ① 以前学过的数据结构没有理解透

    ② 本题卡cin!!,单个字符输入,为避空格可以直接按字符串输入啊

    对拍数据生成代码:

    #include<cstdio>
    #include<ctime>
    #include<cstdlib>
    using namespace std;
    bool v[100];
    int main()
    {
        freopen("data","w",stdout);
        srand(time(0));
        int n=rand()%10+1,m=rand()%n;
        n+=2;
        printf("%d %d
    ",n,m);
        for(int i=1;i<=n;i++)
        {
            int a=rand()%n+1;
            while(v[a]) a=rand()%n+1;
            v[a]=true;
            printf("%d ",a);
        }
        printf("
    ");
        for(int i=1;i<=m;i++)
        {
            int a=rand()%n+1,b=rand()%n+1;
            while(a==b) b=rand()%n+1;
            printf("%d %d
    ",a,b);
        }
        int p=rand()%10+5;
        printf("%d
    ",p);
        for(int i=1;i<=p;i++)
        {
            if(i<=p/2) printf("B ");
            else printf("Q ");
            int a=rand()%n+1,b=rand()%n+1;
            while(a==b) b=rand()%n+1;
            printf("%d %d
    ",a,b);
        }
    }
    View Code
  • 相关阅读:
    Does Oracle Goldengate support Parallel DML?
    Error accessing PRODUCT_USER_PROFILE?
    数据库基础服务SLA模板
    SQL脚本:监控当前重做日志文件使用情况
    Mysql:mysql 控制台程序的提示符 prompt 字符串设置
    Mysql:开启了二进制日志功能 logbin 的mysql数据库, 如何故障恢复?
    Mysql:datetime,time,timestamp精确度只能到 秒(second),毫秒\微秒 只存在于 "文字值\某些函数 参数or返回值"中!
    Sqlserver 2005 配置 数据库镜像:强制服务(可能造成数据丢失):使镜像数据库 强制成为 主数据库
    Sqlserver 2005 配置 数据库镜像:Mirror 的注意事项!!!!!!!!!
    C++ GetTickCount函数
  • 原文地址:https://www.cnblogs.com/TheRoadToTheGold/p/6520714.html
Copyright © 2020-2023  润新知