并查集及扩展域与边带权

并查集是个很实用的数据结构,主要功能可以动态维护若干个不重叠的集合,并支持合并与查询,当然这些都只是概念,个人理解并查集能够维护具有传递性的关系,还可有维护节点之间的连续性,克鲁斯卡尔就是这样做到的.普通的并查集就是如此,今天介绍并查集的扩展,看题:

并查集及扩展域与边带权

并查集及扩展域与边带权

题目一看便知道要用到并查集,判断在不在一列可以普通的并查集就能实现,可是如何实现了解两艘战舰之间的距离呢?

我刚开始也是直接暴力枚举,加上一个son数组和dis数组,dis表示点到点所在树的根的距离,每次合并两个树时,暴力修改。

最后辛辛苦苦把代码写完,就得了50,以下是暴力代码:

并查集及扩展域与边带权
#include<bits/stdc++.h>
using namespace std;
const int maxn=31000;
int f[maxn],t,son[maxn],dis[maxn];
char ch;
inline int read()
{
    int x=0,ff=1;
    char ch=getchar();
    while(!isdigit(ch))
    {
        if(ch=='-') ff=-1;
        ch=getchar();
    }
    while(isdigit(ch))
    {
        x=(x<<1)+(x<<3)+(ch^48);
        ch=getchar();
    }
    return x*ff;
}
inline void put(int x)
{
    if(x<0) putchar('-'),x=-x;
    if(x>9) put(x/10);
    putchar(x%10+'0');
}
inline int getf(int x)
{
    if(f[x]==x) return x;
    else return (getf(f[x]));
}
inline int gets(int x)
{
    if(son[x]==x) return x;
    else return (gets(son[x]));
}
inline void work1(int tou,int wei)
{
    f[tou]=wei;son[wei]=tou;
    int i=tou;
    while(1)
    {
        if(i==son[i]) {dis[i]=dis[f[i]]+1;break;}
        dis[i]=dis[f[i]]+1;
        i=son[i];
    }
}
int main()
{
    //freopen("1.in","r",stdin);
    t=read();
    for(int i=1;i<=30000;i++) f[i]=i,son[i]=i;
    for(int i=1;i<=t;i++)
    {
        cin>>ch;
        int x=read(),y=read();
        int t1=getf(x),t2=gets(x);
        int t3=getf(y),t4=gets(y);
        if(ch=='M') work1(t1,t4);
        if(ch=='C') 
        {
            if(t1==t3) cout<<abs(dis[x]-dis[y])-1<<endl;
            else       cout<<-1<<endl;
        }
    }
    return 0;
}
View Code

实在不行就去看了算法进阶,才知到不需维护各个点到根的距离,那样复杂度太高,可以维护每个点与它f[x]的权值(也可理解为从根开始的编号,根不算在内),为什么呢,因为这样你在找父亲与路径压缩时就可以顺便把点x到根的距离解决,因为每次路径压缩都是将路上各点单独拿出来与根相连,距离也就可以解决。找父亲代码:

inline int getf(int x)
{
    if(f[x]==x) return x; //找到根,返回。 
    int root=getf(f[x]); //找根。 
    dis[x]+=dis[f[x]];  //因为此时经过回溯,dis[f[x]]的距离已经是 f[x]到根的距离了,所以 
    return f[x]=root;  //dis[x]只需将原本与父亲相连的权值加上父亲的距离即是与跟的距离。 
}                     //最后加上路径压缩. 

解决了找父亲,接下来就是合并了,两个子树合并,需要求出x的到y的距离(x,y为两个子树的根,且x要到y上),显然,dis[s]便是y的size(),所以还需维护以个size()数组表示各个集合的点数量,

代码:

inline void merge(int x,int y)
{
    f[x]=y;        
    dis[x]=size[y];
    size[y]+=size[x];
}

把这些解决了,题目也就没那么难了,总代码:

并查集及扩展域与边带权
#include<bits/stdc++.h>
using namespace std;
const int maxn=31000;
int f[maxn],t,dis[maxn],size[maxn];
char ch;
inline int read()
{
    int x=0,ff=1;
    char ch=getchar();
    while(!isdigit(ch))
    {
        if(ch=='-') ff=-1;
        ch=getchar();
    }
    while(isdigit(ch))
    {
        x=(x<<1)+(x<<3)+(ch^48);
        ch=getchar();
    }
    return x*ff;
}
inline void put(int x)
{
    if(x<0) putchar('-'),x=-x;
    if(x>9) put(x/10);
    putchar(x%10+'0');
}
inline int getf(int x)
{
    if(f[x]==x) return x; //找到根,返回。 
    int root=getf(f[x]); //找根。 
    dis[x]+=dis[f[x]];  //因为此时经过回溯,dis[f[x]]的距离已经是 f[x]到根的距离了,所以 
    return f[x]=root;  //dis[x]只需将原本与父亲相连的权值加上父亲的距离即是与跟的距离。 
}                     //最后加上路径压缩. 
inline void merge(int x,int y)
{
    f[x]=y;        
    dis[x]=size[y];
    size[y]+=size[x];
}
int main()
{
    freopen("1.in","r",stdin);
    t=read();
    for(int i=1;i<=30000;i++) f[i]=i,size[i]=1;
    for(int i=1;i<=t;i++)
    {
        cin>>ch;
        int x=read(),y=read();
        int t1=getf(x);
        int t2=getf(y);
        if(ch=='M') merge(t1,t2);
        if(ch=='C') 
        {
            if(t1==t2) put(abs(dis[x]-dis[y])-1),cout<<endl;
            else       put(-1),cout<<endl;
        }
    }
    return 0;
}
View Code

 

上一篇:P1536 村村通


下一篇:P1955 [NOI2015]程序自动分析