并查集——亲戚

  废话不多说,直接看题:

  一看这道题,我就有了思路:既然这道题身在图论板块,那么就要用图的存储、操作方法来解决,先开一个二维数组a[20001][20001],把初值尽可能赋大,再输入数据,并建立关系,然后用floyed算法,虽然不用求最短路径,但是至少能知道两人的关系能否通过中继联通,如果结果正常(即a[i][j]!=999999),则输出“Yes”,否则输出“No”。

  代码如下:

#include<iostream>
using namespace std;
int a[20001][20001],n,m,q;int x,y;
int main()
{
    cin>>n>>m;
    for(int i=1;i<=n;i++)
    for(int j=1;j<=n;j++)
    {
        a[i][j]=999999;//初始化,方便floyed算法 
    }
    for(int i=1;i<=n;i++)
    a[i][i]=0;//自己和自己当然不是亲戚
    for(int i=1;i<=m;i++)
    {
        cin>>x>>y;
        a[x][y]=1;//建立关系
        a[y][x]=1;
    }
    for(int k=1;k<=n;k++)
    for(int i=1;i<=n;i++)
    for(int j=1;j<=n;j++)
    {
        if(a[i][j]>a[i][k]+a[k][j])//floyed算法
        a[i][j]=a[i][k]+a[k][j];
    }
    cin>>q;
    while(q--)
    {
        cin>>x>>y;
        if(a[x][y]!=999999)
        cout<<"Yes"<<endl;//有关系
        else cout<<"No"<<endl;//无关系
    }
    return 0;
}

   

  可是结果却不容乐观,花式错误:运行超时,运行错误,内存超限。通过这道题我明白了电脑内存有多么脆弱,才4*10^8个数,我平时十分珍惜空间大小,每一次都开小数组,难得浪一次,竟然爆空间了。我仍不死心,又换成深搜试了试,结果连样例都过不了~~,代码就不奉上了;后来我有恶补比floyed算法快的其他最短路径算法(如dijkstra),发现时间复杂度也好不到哪去。后来回头一看题,竟然关系就有100,0000个,询问有10,0000次,这是让我O(n)做完吗,还要用一维数组,后来听说要用并查集。

  那么并查集是什么?这并查集得分开看每一个字;前两个字“并”和“查”都是并查集的基本操作,稍后自然会说;“集”则代表这些数是一个个集合。“天生我才必有用”,并查集比起数组来说,更节省空间,二维数组10000*10000就爆了,怎能容得下这道题的数据范围呢?而并查集用的只是一维数组;而且并查集比起floyed算法来说,时间复杂度也只有O(n),比floyed O(n^3)好多了。

接下来就以此题为例,介绍一下并查集的基本操作

1)初始化并查集:把每一个数据都先初始成一个单独的集合;

2)查找:不断递归/循环找到指定数据的父节点,直到找到其祖先节点,其中并查集数组每一元素存储的是其父节点编号,祖先节点满足无父节点,也就是其存储值仍为初始化时的值,即其编号等于其存储的值(因为初始化时为了分成不同集合把存储值赋成其编号);祖先节点并非真的是祖先,其实就是一个标志,只要值为同一祖先节点编号的元素,都能判断他们在同一集合中,节省了时间;

3)合并:如果两人祖先不同,且数据给出他们有亲缘关系,就可以将他们合并到同一祖先下;

4)判断元素是否在同一集合中:判断二人祖先是否相同,如果相同,则在同一集合内;否则在不同集合。

废话不多说,为了更好理解,特呈上手写模板,可以与上文结合看:

#include<iostream>
using namespace std;
int a[20001],n,m,x,y,q,x2,y2;
void setup()//初始化并查集 
{
    for(int i=1;i<=n;i++)//共有n个人,每个人都要初始化 
    {
        a[i]=i;//初始化每个人都是独立的集合 
    }
}
int find(int x)//查找祖先 
{
    if(a[x]==x) return x;//找到祖先立即返回 
    else return a[x]=find(a[x]);//否则继续寻找+优化(路径压缩) 
}
void mix(int x,int y)//合并至同一集合 
{
    x=find(x);y=find(y);//计算两人的祖先 
    if(x!=y) a[x]=y;//若不相同则将x合并到y的祖先下 
}
bool is_relative(int x,int y)//判断是否有亲属关系 
{
    return find(x)==find(y);//查看两人祖先,判断是否相同并返回布尔值以便输出 
}
int main()
{
    cin>>n>>m;
    setup();//初始化 
    for(int i=1;i<=m;i++)
    {
        cin>>x2>>y2;
        mix(x2,y2);//合并到同一集合 
    }
    cin>>q;
    while(q--)
    {
        cin>>x2>>y2;
        if(is_relative(x2,y2)) cout<<"Yes"<<endl;//有关系 
        else cout<<"No"<<endl;//没关系 
    }
    return 0;
}

  要AC代码的同志切勿粘贴,别说我没说过,这只是个模板。小编习惯用cin,cout再加上多次使用函数,会使速度变慢,但是改过之后发现仍不能过,代码就算了,于是又研究并查集的优化方法,提升速度,常规优化方法如下:

1)路径压缩:听到这个词,小编就想路径压缩麻烦吗?会有什么好处?既然压缩,又怎么压缩?不用担心,小编会一一解答。其实小编代码中的find函数已经在用路径压缩了,可以和我下面讲的相结合来看。其实也好理解,下面是小编一时兴起画的,技术不好,见谅。

 

  如图所示左图为路径压缩前的亲戚关系图,要确定4和6有无亲戚关系则需要浪费更多的时间;但右图是压缩路径后的亲戚关系图,能很好地表现出各节点到祖先节点的关系,查找次数也由此减少。但路径压缩的弊端也显露出来,这样虽然能表现出各节点和祖先节点的关系,但却破坏了原本的结构,无法判断各节点到父节点的直接关系,在个别题目不能使用。

2)按秩合并:这个优化方法既麻烦又不怎么常见,有两种,按大小合并和按深度合并,大小合并很简单,小的合到大的集合中呗;深度合并则考虑的更多,将关系当成树,把深度小的合并到深度大的上。优点是既能节约时间还能保留好原有关系,但是没有路径压缩快,此题不宜用,也就不详解了(其实是小编不太会),知道思想就行。

   优化之后就大功告成了,小编心中尚存一丝侥幸,据说“std::ios::sync_with_stdio(false);可以使cin,cout和scanf,printf速度相差无几”,这是在《信息学奥赛一本通》上看到的,应该挺有用的吧,小编试了一下,切,还相差无几,简直和原来一样,依旧是四个测试点没过超时,最终全部改成scanf,printf就通过了。行了,不说了,代码胜于雄辩,AC代码呈上:

#include<cstdio>
using namespace std;
int a[20001],n,m,sum,p,q;
/*void setup() //初始化每一个集合 
{
    for(int i=1;i<=n;i++)
    {
        a[i]=i;//每一个人都初始化为一个单独集合 
    }
}*/
int find(int x)//不断寻找父节点+路径压缩 
{
    if(a[x]==x) return x;//找到尽头,即祖先节点 
    else return find(a[x]);
}
/*void mix(int x,int y)//合并各个集合 
{
    x=find(x);y=find(y);
    if(x!=y) a[x]=y;//将x和y合并到同一祖先下 
}*/
/*bool is_relative(int x,int y)//判断是否同一祖先,是否在同一集合 
{
    return find(x)==find(y);
}*/
int main()
{
    scanf("%d%d",&n,&m);
    for(int i=1;i<=n;i++)
    {
        a[i]=i;//每一个人都初始化为一个单独集合 
    }
    for(int i=1;i<=m;i++)
    {
        scanf("%d%d",&p,&q);
        int r1=find(p);
        int r2=find(q);
        if(r1!=r2) a[r2]=r1;
    }
    scanf("%d",&sum);
    while(sum--)
    {
        int x,y;
        scanf("%d%d",&x,&y);
        if(find(x)==find(y)) printf("Yes \n");
        else printf("No \n");
    }
    return 0;
}
posted @ 2019-01-24 21:55  c1714-gzr  阅读(1151)  评论(0编辑  收藏  举报