并查集-最小生成树,带权并查集,种类并查集。

NEFU大一ACM集训(一)
并查集:
一:并查集的定义
二:并查集的过程以及基本操作
三:并查集的应用:

  • 对于定义的使用——判断图是否是树
  • 最小生成树问题
    四:两种特殊的并查集:
  • 边带权并查集
  • 种类并查集

四:相应的题目

并查集的定义: 并查集是一种树型的数据结构,用于动态处理一些不相交集合(Disjoint Sets)的合并及查询问题。
并查集的过程以及基本操作:

  1. 过程: 运用并查集的过程就是先将每一个元素看成一个独立的集合,这个时候每一个集合的标识,再将不同的元素(集合)两两合并到一个集合当中,这就是并查集的合并,在合并的过程中这两个元素就连接在一起了,其中一个元素变成儿子,一个元素变成了父亲,以此类推那么一个集合中就会只有一个总父亲,总父亲的父亲是它自己,其他相对都是它儿子或者儿子的n次方,如果我们要找一个元素在不在一个集合当中,只要找到它的总父亲就行。
    在这里插入图片描述
    2.基本操作:
    创造并查集:我们使用以一个数组的形式去模拟链表,假设有一个pre[i]的数组,i表示着第几个元素,而pre[i]的值表示的是这个元素的父亲,一开始只要把他们的的父亲设置成他们自己即可。
    查找元素是否在相同的集合: 通过一个循环不断地去向上找,找到一个pre[i]=i的元素即可。
    普通写法:
int find_(int x)
{
    int r=x;
    while(r!=pre[r])r=pre[r];
    return r;
}

压缩写法:即把同一个总父亲的所有元素直接与总父亲相连,这样在查找的时候就可以缩短树的长度。在这里我提供两种写法:一种是循环写法,一种是递归写法

循环写法:

int find_(int x)
{
    int r=x;
    while(r!=pre[r])r=pre[r];
    int tmp,i=x;
    while(i!=r)
    {
        tmp=pre[i];
        pre[i]=r;
        i=tmp;
    }
    return r;
}

递归写法:

int find_(int x)
{
    if(pre[x]==x)return pre[x];
    int fn=find_(pre[x]);
    return pre[x]=fn;
}

合并集合:两个不同的总父亲所代表的集合,只要把一个总父亲接到另一个总父亲身上,将其自身变为儿子,即可。

void merge_(int x,int y)
{
    int tmp1=find_(x);
    int tmp2=find_(y);
    if(tmp1!=tmp2)pre[tmp1]=tmp2;
}

并查集的应用:
一:判断图是否是树,既然每个树的节点都是互通的,那么只要找一个节点的总父亲,就是这个树的总父亲,如果有节点的父亲是它本身且不是总父亲,那么这个图必然不是树。
二:最小生成树:在一给定的无向图G = (V, E) 中,(u, v) 代表连接顶点 u 与顶点 v 的边(即),而 w(u, v) 代表此边的权重,若存在 T 为 E 的子集(即)且为无循环图,使得的 w(T) 最小,则此 T 为 G 的最小生成树并且最小生成树是最小权重生成树的简称。
在这里插入图片描述
模板代码:这里的代码是基于Kruskal(克鲁斯卡尔)算法:求最小生成树的话给它的权值从小到大排序,然后将两个点按照权值的大小的顺序连起来,如果两个点已经连接则不需要连接,当树的节点达到图的节点减一时则不再连接,得到的树就是最小生成树,它本质还是用贪心得到的。

#include <bits/stdc++.h>
using namespace std;
const int N=1e3;
int pre[N];
struct node
{
    int u,v,c;
}vis[N];//用结构体包含起点终点和权值
bool cmp(struct node x,struct node y)//权值排序
{
    return x.c<y.c;
}
int find_(int x)//找父亲
{
    int r=x;
    while(r!=pre[r])r=pre[r];
    int tmp,i=x;
    while(i!=r)
    {
        tmp=pre[i];
        pre[i]=r;
        i=tmp;
    }
    return r;
}
void merge_(int x,int y)//合并
{
    int tmp1=find_(x);
    int tmp2=find_(y);
    if(tmp1!=tmp2)pre[tmp1]=tmp2;
}
int main()
{
    int n,m;//n是点的个数,m是边的条数。
    while(cin>>n>>m)
    {
        int num;
        num=0;
        memset(pre,0,sizeof(pre));
        for(int i=1;i<=n;i++)pre[i]=i;
        for(int i=1;i<=m;i++)
        {
           cin>>vis[i].u>>vis[i].v>>vis[i].c>>vis[i].j;
        }
        sort(vis+1,vis+1+m,cmp);
        for(int i=1;i<=m;i++)
        {
            int tmp2=find_(vis[i].u);
            int tmp3=find_(vis[i].v);
            if(tmp2!=tmp3)//判断两点是否要相连
            {
                merge_(vis[i].u,vis[i].v);
                num++;
            }
            if(num==n-1)break;//这个if不能放到循环的开始,不然会有只走一次且合格的数据会被判定不正确
        }
    }
    return 0;
}

两种特殊的并查集:

  • 边带权并查集:这里的权值是指的是每个点到达其所在的总父亲节点的长度,那么我们一共需要三个数组,一个数组表示每个节点的父亲,一个数组表示每个节点到达其所在的总父亲的长度,一个数组表示每个节点所在的集合的大小,因为在两个总父亲A,B合并的时候会使成为儿子A的一方的顶点到达新的总父亲B一方的长度变为之前B的集合的大小,而新的父亲的集合大小变为A的集合大小加B的集合大小,有一个特点是每次查找都要从新更新一下,因为合并完以后它其实是并没有更新每个点到新的总父亲的距离的,只更新的以前的总父亲到新的总父亲,还有目前所在集合的大小,所以在找点的时候仍然要在查找一下,更新每个节点到新总父亲的距离。
    题目:
    Noip-P1196银河英雄传说
    Noip-P2342搭积木
    (题目的答案与思路放到后面Noip的题目中)
  • 种类并查集:普通的并查集只能表示一种的关系的两个分支,就像是一群人它是好人或者不是好人,而不能表示一群人中里面的有群好人,一群坏人,一群既不是好人也不是坏人(这个时候最可爱的的你可能会想,人不是好人不就是坏人吗 )。这个时候我们就要拓展并查集的域即并查集的大小,并根据分段来分不同的种类,首先根据不同域之间的关系判断元素在哪个域,处理不同域之间的关系直接处理就行,处理相同域的关系时,要同时处理所有的域中的要处理的元素。
    题目:
    P2024-食物链
    P1892-团伙
    (题目的答案与思路放到后面Noip的题目中)
    通过这几个题目更加强调了,并查集是对于点和点之间边的意义的强调而不是对点的强调。
    相应的题目:
    1.Noip-P3366
    思路:模板题,打完跑路,不熟悉可多手打几次。
#include <bits/stdc++.h>
using namespace std;
const int N=2e5+5;
int pre[N];
int m,n;
struct node
{
    int x,y,z;
}vis[N];
bool cmp(struct node x,struct node y)
{
    return x.z<y.z;
}
int find_(int x)
{
    int r=x;
    while(r!=pre[r])r=pre[r];
    int tmp,i=x;
    while(i!=r)
    {
        tmp=pre[i];
        pre[i]=r;
        i=tmp;
    }
    return r;
}
void merge_(int x,int y)
{
    int tmp1=find_(x);
    int tmp2=find_(y);
    if(tmp1!=tmp2)pre[tmp1]=tmp2;
}
int main()
{
    ios::sync_with_stdio(false);
    cin>>n>>m;
    for(int i=1;i<=m;i++)pre[i]=i;
    for(int i=1;i<=m;i++)
        cin>>vis[i].x>>vis[i].y>>vis[i].z;
    sort(vis+1,vis+1+m,cmp);
    int sum,num;
    sum=num=0;
    for(int i=1;i<=m;i++)
    {
        if(num==n-1)break;
        int x,y,z,tmp1,tmp2;
        x=vis[i].x;y=vis[i].y;z=vis[i].z;
        tmp1=find_(x);tmp2=find_(y);
        if(tmp1!=tmp2)
        {
            merge_(x,y);
            sum+=z;
            num++;
        }
    }
    cout<<sum<<endl;
    return 0;
}

2.Noip-P1536
思路:把点都合并完,看看有谁的父亲还是自己,数完以后减一,减去一个连接着很多儿子的总父亲就行了。

#include<bits/stdc++.h>
using namespace std;
const int N=1e3+5;
int pre[N];
int find_(int x)
{
    int r=x;
    while(r!=pre[r])r=pre[r];
    int tmp,i=x;
    while(i!=r)
    {
        tmp=pre[i];
        pre[i]=r;
        i=tmp;
    }
    return r;
}
void merge_(int x,int y)
{
    int tmp1=find_(x);
    int tmp2=find_(y);
    if(tmp1!=tmp2)pre[tmp1]=tmp2;
}
int main()
{
    ios::sync_with_stdio(false);
    int n,m,x,y,sum;
    while(cin>>n>>m)
    {
        if(n==0)break;
        sum=0;
        for(int i=1; i<=n; i++)pre[i]=i;
        for(int i=1; i<=m; i++)
        {
            cin>>x>>y;
            merge_(x,y);
        }
        for(int i=1; i<=n; i++)
        {
            if(pre[i]==i)sum++;
        }
        sum--;
        cout<<sum<<endl;
    }
    return 0;
}

3.Noip-P2820
思路:也是一道最小生成树的题,它要减去的边最大,那么我先求减去的边最小之和再拿总边减这个和就行了,也是一道模板题。

#include<bits/stdc++.h>
using namespace std;
const int N=1e3+5;
int pre[N];
struct node
{
    int i,j,m;
}vis[N];
bool cmp(struct node x, struct node y)
{
    return x.m<y.m;
}
int find_(int x)
{
    int r=x;
    while(r!=pre[r])r=pre[r];
    int tmp,i=x;
    while(i!=r)
    {
        tmp=pre[i];
        pre[i]=r;
        i=tmp;
    }
    return r;
}
void merge_(int x,int y)
{
    int tmp1=find_(x);
    int tmp2=find_(y);
    if(tmp1!=tmp2)pre[tmp1]=tmp2;
}
int main()
{
    int n,k,tmp,sum,num,res;
    tmp=sum=num=0;
    cin>>n>>k;
    for(int i=1;i<=n;i++)pre[i]=i;
    for(int i=1;i<=k;i++)
    {
        cin>>vis[i].i>>vis[i].j>>vis[i].m;
        sum+=vis[i].m;
    }
    sort(vis+1,vis+1+k,cmp);
    for(int i=1;i<=k;i++)
    {
        if(num==n-1)break;
        int tmp1=find_(vis[i].i);
        int tmp2=find_(vis[i].j);
        if(tmp1!=tmp2)
        {
            merge_(vis[i].i,vis[i].j);
            tmp+=vis[i].m;
            num++;
        }
    }
    res=sum-tmp;
    cout<<res<<endl;
    return 0;
}

4.Noip-P1547
思路:最小生成树然后找到最大的边长就可以了。

#include<bits/stdc++.h>
using namespace std;
const int N=1e4+5;
int pre[N];
struct node
{
    int a,b,l;
}vis[N];
bool cmp(struct node x,struct node y)
{
    return x.l<y.l;
}
int find_(int x)
{
    int r=x;
    while(r!=pre[r])r=pre[r];
    int tmp,i=x;
    while(i!=r)
    {
        tmp=pre[i];
        pre[i]=r;
        i=tmp;
    }
    return r;
}
void merge_(int x,int y)
{
    int tmp1=find_(x);
    int tmp2=find_(y);
    if(tmp1!=tmp2)pre[tmp1]=tmp2;
}
int main()
{
    int n,m,sum,num;
    sum=num=0;
    cin>>n>>m;
    for(int i=1;i<=n;i++)pre[i]=i;
    for(int i=1;i<=m;i++)
        cin>>vis[i].a>>vis[i].b>>vis[i].l;
    sort(vis+1,vis+1+m,cmp);
    for(int i=1;i<=m;i++)
    {
        if(num==n-1)break;
        int tmp1=find_(vis[i].a);
        int tmp2=find_(vis[i].b);
        if(tmp1!=tmp2)
        {
            merge_(vis[i].a,vis[i].b);
            sum=max(sum,vis[i].l);
            num++;
        }
    }
    cout<<sum<<endl;
    return 0;
}

5.Noip-P1195
思路:这里的变化是连成K个棉花糖,之前的最小生成树是连出一个树即n个点连出n-1个边,而连出k个树就是n个点连接n-k个边。

#include<bits/stdc++.h>
using namespace std;
const int N=1e4+5;
int pre[N];
struct node
{
    int a,b,l;
} vis[N];
bool cmp(struct node x,struct node y)
{
    return x.l<y.l;
}
int find_(int x)
{
    int r=x;
    while(r!=pre[r])r=pre[r];
    int tmp,i=x;
    while(i!=r)
    {
        tmp=pre[i];
        pre[i]=r;
        i=tmp;
    }
    return r;
}
void merge_(int x,int y)
{
    int tmp1=find_(x);
    int tmp2=find_(y);
    if(tmp1!=tmp2)pre[tmp1]=tmp2;
}
int main()
{
    int n,m,k,sum,num,flag=0;
    cin>>n>>m>>k;
    sum=num=0;
    for(int i=1; i<=n; i++)pre[i]=i;
    for(int i=1; i<=m; i++)
        cin>>vis[i].a>>vis[i].b>>vis[i].l;
    sort(vis+1,vis+1+m,cmp);
    for(int i=1; i<=m; i++)
    {
        if(num==n-k){flag=1;break;}
        int tmp1=find_(vis[i].a);
        int tmp2=find_(vis[i].b);
        if(tmp1!=tmp2)
        {
            merge_(vis[i].a,vis[i].b);
            sum+=vis[i].l;
            num++;
        }
    }
    if(flag)cout<<sum<<endl;
    else cout<<"No Answer"<<endl;
    return 0;
}

6.Noip-P1196银河英雄传说
思路:开三个数组,每次在找父亲的时候记录每个点回溯到总父亲的长度,路径压缩的时候推荐用递归算法,不仅仅是他只需要写4行,而是用while的路径压缩的时候只跑了一次,并没有对每个节点进行回溯,合并的时候注意顶点到合并后顶点的距离,以及合并后两个顶点所代表的的集合长度的大小。

#include<bits/stdc++.h>
using namespace std;
const int N=3e4+5;
int pre[N];
int dis[N],siz[N];
int find_(int x)
{
    int contain,r=x;
    while(r!=pre[r])r=pre[r];
    int tmp,i=x;
    while(i!=r)
    {
        tmp=pre[i];
        dis[i]+=dis[tmp];
        pre[i]=r;
        i=tmp;
    }
    return r;
}
void merge_(int x,int y)
{
    int tmp1=find_(x);
    int tmp2=find_(y);
    if(tmp1!=tmp2)
    {
        pre[tmp1]=tmp2;
        dis[tmp1]+=siz[tmp2];
        siz[tmp1]+=siz[tmp2];
        siz[tmp2]=siz[tmp1];
    }
}

int main()
{
    int t;
    cin>>t;
    for(int i=1; i<=N; i++)
    {
        pre[i]=i;
        dis[i]=0;
        siz[i]=1;
    }
    while(t--)
    {
        char judge;
        int i,j;
        cin>>judge>>i>>j;
        if(judge=='M')merge_(i,j);
        else
        {
            int tmp1=find_(i);
            int tmp2=find_(j);
            if(tmp1!=tmp2)cout<<"-1"<<endl;
            else
            {
                int sum=0;
                sum=abs(dis[i]-dis[j])-1;
                cout<<sum<<endl;
            }
        }
    }
    return 0;
}

7.Noip-P2342搭积木
思路:还是带权并查集,但是要注意一个特点就是即使查找一个的时候也要重新更新一下,它到总父亲的长度,不然永远无法AC。

#include<bits/stdc++.h>
using namespace std;
const int N=1e+5;
int pre[N],dis[N],siz[N],vis[N];
int find_(int x)
{
    if(x==pre[x])return x;
    int tmp=find_(pre[x]);
    dis[x]+=dis[pre[x]];
    return pre[x]=tmp;
}
int merge_(int x,int y)
{
    int tmp1=find_(x);
    int tmp2=find_(y);
    if(tmp1!=tmp2)
    {
        pre[tmp1]=tmp2;
        dis[tmp1]=siz[tmp2];
        siz[tmp2]+=siz[tmp1];
    }
}
int main()
{
    ios::sync_with_stdio(false);
    int p;
    cin>>p;
    for(int i=1;i<=N;i++){pre[i]=i;dis[i]=0;siz[i]=1;}
    while(p--)
    {
        char judge;
        int x,y,z;
        cin>>judge;
        if(judge=='M'){cin>>x>>y;merge_(x,y);}
        else
        {
            cin>>z;
            find_(z);
            cout<<dis[z]<<endl;
        }
    }
    return 0;
}

8.P2024-食物链
思路:先通过谁吃谁的关系判断它在哪个域中,如果它被吃或者吃别人而在相同的域必然说谎,或者它时吃别人的却被吃或它被吃却吃别人则它必然说谎,一定要判断到第二种情况,而第二种情况的A,B,C的三个域中只要是元素在任何一个域都会产生不同的效果,所以第一种情况的反面不是第二种情况并且第二种情况要复杂得多,判断出是相同域的操作是同时操作三个域里面的两个元素,这两个题我过几天写两个专门的题解放着。

#include<bits/stdc++.h>
using namespace std;
const int N=1e5+5;
int pre[N*3];
int find_(int x)
{
    int r=x;
    while(r!=pre[r])r=pre[r];
    return r;
}
int main()
{
    int n,k,j,x,y,res=0;
    ios::sync_with_stdio(false);
    cin>>n>>k;
    for(int i=1;i<=n*3;i++)pre[i]=i;
    for(int i=1;i<=k;i++)
    {
        cin>>j>>x>>y;
        if(x>n||y>n){res++;continue;}
        else
        {
            if(j==1)
            {
                if((find_(x+n)==find_(y))||(find_(x)==find_(y+n))){res++;}
                else
                {
                    pre[find_(x)]=find_(y);
                    pre[find_(x+n)]=find_(y+n);
                    pre[find_(x+n+n)]=find_(y+n+n);
                }
            }
            else
            {
                if((find_(x)==find_(y))||(find_(x)==find_(y+n))){res++;}
                else
                {
                    pre[find_(x+n)]=find_(y);
                    pre[find_(x+n+n)]=find_(y+n);
                    pre[find_(x)]=find_(y+n+n);
                }
            }
        }
    }
    cout<<res<<endl;
    return 0;
}

以下题目来源于东北林业大学OJ网:http://acm.nefu.edu.cn/problem.php
有兴趣可以直接搜题目名称做题。
1.畅通工程并查集版
思路:模板题,打完跑路。

#include <bits/stdc++.h>
using namespace std;
const int N=1e3+5;
int pre[N];
int find_(int x)
{
    int r=x;
    while(r!=pre[r])r=pre[r];
    int tmp,i=x;
    while(i!=r)
    {
        tmp=pre[i];
        pre[i]=r;
        i=tmp;
    }
    return r;
}
void merge_(int x,int y)
{
    int tmp1=find_(x);
    int tmp2=find_(y);
    if(tmp1!=tmp2)pre[tmp1]=tmp2;
}
int main()
{
    ios::sync_with_stdio(false);
    int n,m,u,v,sum;
    while(cin>>n>>m)
    {
        if(n==0)break;
        sum=0;
        for(int i=1;i<=n;i++)pre[i]=i;
        for(int i=1;i<=m;i++)
        {
            cin>>u>>v;
            merge_(u,v);
        }
        for(int i=1;i<=n;i++)
            if(pre[i]==i)sum++;
        sum--;
        cout<<sum<<endl;
    }
    return 0;
}

2.小希的迷宫
思路:因为它一定是树,所以m=n-1不然就不是树,然后用一个桶排序记录它是否存在就行了,当然用set更快。

#include <bits/stdc++.h>
using namespace std;
const int N=1e5+5;
int pre[N];
bool vis[N];
int main()
{
    int x,y,num,sum,maxn,flag=1;
    while(flag)
    {
        num=sum=maxn=0;
        memset(vis,0,sizeof(vis));
        memset(pre,0,sizeof(pre));
        while(scanf("%d %d",&x,&y)&&x&&y)
        {
            if(x==-1&&y==-1){flag=0;break;}
            vis[x]=1;vis[y]=1;
            maxn=max(x,maxn);
            maxn=max(y,maxn);
            sum++;
        }
        if(!flag)break;
       for(int i=1;i<=maxn;i++)if(vis[i]==1)num++;
       if(sum==num-1)printf("Yes\n");
       else printf("No\n");
    }
    return 0;
}

3.湖南修路
思路:注意一点最后面判断是1的时候要插进去。

#include <bits/stdc++.h>
using namespace std;
const int N=1e3;
int pre[N];
struct node
{
    int u,v,c,j;
}vis[N];
bool cmp(struct node x,struct node y)
{
    return x.c<y.c;
}
int find_(int x)
{
    int r=x;
    while(r!=pre[r])r=pre[r];
    int tmp,i=x;
    while(i!=r)
    {
        tmp=pre[i];
        pre[i]=r;
        i=tmp;
    }
    return r;
}
void merge_(int x,int y)
{
    int tmp1=find_(x);
    int tmp2=find_(y);
    if(tmp1!=tmp2)pre[tmp1]=tmp2;
}
int main()
{
    int n;
    while(cin>>n)
    {
        int tmp,sum,res,num;
        sum=res=num=0;
        memset(pre,0,sizeof(pre));
        for(int i=1;i<=n;i++)pre[i]=i;
        tmp=n*(n-1)/2;
        for(int i=1;i<=tmp;i++)
        {
           cin>>vis[i].u>>vis[i].v>>vis[i].c>>vis[i].j;
           sum+=vis[i].c;
           if(vis[i].j)merge_(vis[i].u,vis[i].v);
        }
        sort(vis+1,vis+1+tmp,cmp);
        for(int i=1;i<=tmp;i++)
        {
            if(num==n-1)break;
            int tmp2=find_(vis[i].u);
            int tmp3=find_(vis[i].v);
            if(tmp2!=tmp3&&!vis[i].j)
            {
                merge_(vis[i].u,vis[i].v);
                res+=vis[i].c;
                num++;
            }
        }
        cout<<res<<endl;
    }
    return 0;
}

4.最小树1
思路:把结构体里记录长度的变量变成float型,又整型变成浮点型的时候cmp并没有失效。

#include <bits/stdc++.h>
using namespace std;
const int N=1e3;
int pre[N];
struct node
{
    int u,v;
    float c;
}vis[N];
bool cmp(struct node x,struct node y)
{
    return x.c<y.c;
}
int find_(int x)
{
    int r=x;
    while(r!=pre[r])r=pre[r];
    int tmp,i=x;
    while(i!=r)
    {
        tmp=pre[i];
        pre[i]=r;
        i=tmp;
    }
    return r;
}
void merge_(int x,int y)
{
    int tmp1=find_(x);
    int tmp2=find_(y);
    if(tmp1!=tmp2)pre[tmp1]=tmp2;
}
int main()
{
    int n;
    while(scanf("%d",&n)&&n)
    {
        int tmp,num;
        float sum,res;
        sum=res=num=0;
        memset(pre,0,sizeof(pre));
        for(int i=1;i<=n;i++)pre[i]=i;
        tmp=n*(n-1)/2;
        for(int i=1;i<=tmp;i++)
        {
           scanf("%d %d %f",&vis[i].u,&vis[i].v,&vis[i].c);
           sum+=vis[i].c;
        }
        sort(vis+1,vis+1+tmp,cmp);
        for(int i=1;i<=tmp;i++)
        {
            if(num==n-1)break;
            int tmp2=find_(vis[i].u);
            int tmp3=find_(vis[i].v);
            if(tmp2!=tmp3)
            {
                merge_(vis[i].u,vis[i].v);
                res+=vis[i].c;
                num++;
            }
        }
        printf("%.2f\n",res);
    }
    return 0;
}

5.修路工程
思路:判断能不能生成最小生成树,不能就问好,能就输出。

#include <bits/stdc++.h>
using namespace std;
const int N=1e3;
int pre[N];
struct node
{
    int u,v,c;
}vis[N];
bool cmp(struct node x,struct node y)
{
    return x.c<y.c;
}
int find_(int x)
{
    int r=x;
    while(r!=pre[r])r=pre[r];
    int tmp,i=x;
    while(i!=r)
    {
        tmp=pre[i];
        pre[i]=r;
        i=tmp;
    }
    return r;
}
void merge_(int x,int y)
{
    int tmp1=find_(x);
    int tmp2=find_(y);
    if(tmp1!=tmp2)pre[tmp1]=tmp2;
}
int main()
{
    int n,m;
    while(cin>>n>>m)
    {
        if(n==0)break;
        int num,sum,flag;
        num=sum=flag=0;
        memset(pre,0,sizeof(pre));
        for(int i=1;i<=m;i++)pre[i]=i;
        for(int i=1;i<=n;i++)
            cin>>vis[i].u>>vis[i].v>>vis[i].c;
        sort(vis+1,vis+1+n,cmp);
        for(int i=1;i<=n;i++)
        {
            int tmp2=find_(vis[i].u);
            int tmp3=find_(vis[i].v);
            if(tmp2!=tmp3)
            {
                merge_(vis[i].u,vis[i].v);
                sum+=vis[i].c;
                num++;
            }
            if(num==m-1){flag=1;break;}
        }
        if(flag)cout<<sum<<endl;
        else cout<<"?"<<endl;
    }
    return 0;
}

6.一道图论一其实是loj136
思路:正常的生成最小生成树,由于最小生成树的每个边的权值都是最小的记录下来就完事了,这个题还是问了jsc大佬才整出来的,再次致谢,一开始还想生成树+深搜,有点憨憨。

#include <bits/stdc++.h>
using namespace std;
const int N=1e5+5;
int pre[N];
struct node
{
    int u,v,w;
}vis[N],eqr[N];
bool cmp(struct node x,struct node y)
{
    return x.w<y.w;
}
int find_(int x)
{
    int r=x;
    while(r!=pre[r])r=pre[r];
    int tmp,i=x;
    while(i!=r)
    {
        tmp=pre[i];
        pre[i]=r;
        i=tmp;
    }
    return r;
}
void merge_(int x,int y)
{
    int tmp1=find_(x);
    int tmp2=find_(y);
    if(tmp1!=tmp2)pre[tmp1]=tmp2;
}
int main()
{
    int n,m,k,maxn;
    maxn=0;
    cin>>n>>m>>k;
    for(int i=1;i<=n;i++)pre[i]=i;
    for(int i=1;i<=m;i++)
        cin>>vis[i].u>>vis[i].v>>vis[i].w;
    sort(vis+1,vis+1+m,cmp);
    for(int i=1;i<=k;i++)
    {
        cin>>eqr[i].u>>eqr[i].v;
        eqr[i].w=-1;
    }
    for(int i=1;i<=m;i++)
    {
        merge_(vis[i].u,vis[i].v);
        maxn=vis[i].w;
        for(int j=1;j<=k;j++)
        {
            if(eqr[j].w==-1)
            {
                int tmp3=find_(eqr[j].u);
                int tmp4=find_(eqr[j].v);
                if(tmp3==tmp4){eqr[j].w=maxn;}
            }
        }
    }
    for(int i=1;i<=k;i++)cout<<eqr[i].w<<endl;
    return 0;
}

7.藤原千花的星星图
思路:注意long long型,和数据量很大要开O2优化,不然会超时。

#pragma GCC optimize(2)
#include<bits/stdc++.h>
using namespace std;
const int N=1e6+5;
int pre[N];
struct node
{
    int u,v;
    long long c;
}vis[N];
bool cmp(struct node x,struct node y)
{
    return x.c<y.c;
}
int find_(int x)
{
    int r=x;
    while(r!=pre[r])r=pre[r];
    int tmp,i=x;
    while(i!=r)
    {
        tmp=pre[i];
        pre[i]=r;
        i=tmp;
    }
    return r;
}
void merge_(int x,int y)
{
    int tmp1=find_(x);
    int tmp2=find_(y);
    if(tmp1!=tmp2)pre[tmp1]=tmp2;
}
int main()
{
    int n,m,num,flag;
    long long sum;
    while(scanf("%d %d",&n,&m)!=EOF)
    {
        sum=0;num=flag=0;
        for(int i=1;i<=n;i++)pre[i]=i;
        for(int i=1;i<=m;i++)
            scanf("%d %d %lld",&vis[i].u,&vis[i].v,&vis[i].c);
        sort(vis+1,vis+1+m,cmp);
        for(int i=1;i<=m;i++)
        {
            int tmp1=find_(vis[i].u);
            int tmp2=find_(vis[i].v);
            if(tmp1!=tmp2)
            {
                merge_(vis[i].u,vis[i].v);
                sum+=vis[i].c;
                num++;
            }
            if(num==n-1){flag=1;break;}
        }
        if(flag)printf("%lld\n",sum);
        else printf("-1\n");
    }
    return 0;
}
  • 3
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值