2021 最小生成树+并查集

1 判断有向图是否为树

给出几条边,判断这个有向图是否为树。
从以下三点判断即可:
1.不存在环
2.不存在入度大于1的节点
3.入度为0的节点只有1个
前两点在find函数就可以直接判断,关于第二点一开始没看明白,画个图就能想通

#include<bits/stdc++.h>
using namespace std;   
int fa[100001];
int vis[100001];
int pre[100001];
int istree=1;
int find(int x){
    if(x==fa[x])return x;
    else return fa[x]=find(fa[x]);
}
void init(){
    for(int i=1;i<=100000;i++)
    fa[i]=i;
    memset(vis,0,sizeof(vis));
    memset(pre,0,sizeof(pre));
    istree=1;
}
void merge(int a,int b){
    int xa=find(a);
    int xb=find(b);
     
    if(xa==xb||xb!=b){
        istree=0;//存在环或者某个节点入度超过1
    }else fa[xb]=xa;
}
int main(){
    int T;
    int a,b;
    int n,m;
    int cnt=0;
    while(cin>>a>>b){
        if(a==0&&b==0){
            printf("Case %d is a tree.\n",++cnt);
        }
        else if(a<0||b<0)break;
        init();

        vis[a]++;   
        vis[b]++;
        pre[b]++;
        merge(a,b);
        while(cin>>a>>b,a||b){
            vis[a]++;
            vis[b]++;
            pre[b]++;
            merge(a,b);
        }

        int sum=0;
        for(int i=1;i<=100000;i++){
           if(vis[i]&&pre[i]==0)sum++;//判断是否是森林,即入度为0的节点个数
        }
        if(istree&&sum==1)printf("Case %d is a tree.\n",++cnt);
        else printf("Case %d is not a tree.\n",++cnt);

    }
    system("pause");
    return 0;
}

2 判断拼图需要几口井

需要用一个二维数组记录题目信息,然后对每一个节点考虑上下左右是否有merge的可能。最后计算父亲节点有几个。
在这里插入图片描述

 for(int i=1;i<=m;i++)
        for(int j=1;j<=n;j++){
            for(int k=0;k<4;k++){
                int tempy=i+yy[k];
                int tempx=j+xx[k];
                if(tempy<=0||tempx<=0||tempy>m||tempx>n)continue;
                if(k==0&&arr[maps[i][j]-'A'][0]&&arr[maps[tempy][tempx]-'A'][2])merge((i-1)*n+j,(tempy-1)*n+tempx);
                if(k==1&&arr[maps[i][j]-'A'][1]&&arr[maps[tempy][tempx]-'A'][3])merge((i-1)*n+j,(tempy-1)*n+tempx);
                if(k==2&&arr[maps[i][j]-'A'][2]&&arr[maps[tempy][tempx]-'A'][0])merge((i-1)*n+j,(tempy-1)*n+tempx);
                if(k==3&&arr[maps[i][j]-'A'][3]&&arr[maps[tempy][tempx]-'A'][1])merge((i-1)*n+j,(tempy-1)*n+tempx);

            }
        }

3 求最小生成树中最长边与最短边的差

对于每一次查询,从小到大枚举最短边,对于每一次枚举的最短边,又从最短边到大枚举最长边,当查询的点处于同一个联通分量时结束
在这里插入图片描述

    while(cin>>n>>m){
        cnt=0;//这个题特殊、cnt不能放init里面
        init();
        for(int i=1;i<=m;i++){
            cin>>N[++cnt].a;
            cin>>N[cnt].b;
            cin>>N[cnt].c;
        }
        sort(N+1,N+1+cnt,cmp);
        cin>>q;
        for(int i=1;i<=q;i++){
            cin>>a>>b;
            ans=999999;
            int flag=0;
            for(int j=1;j<=cnt;j++){
                //因为ans为最大边-最小边,所以从最短的边枚举最小边
                //最大边的寻找以a、b同时在一个联通分量中结束
                init();
                for(int k=j;k<=cnt;k++){
                    merge(N[k].a,N[k].b);
                    if(find(a)==find(b)){
                        flag=1;
                        ans=min(ans,N[k].c-N[j].c);
                        break;
                        //已经找到以j为最小边前提下,让ab同根的最大边
                    }
                }
            }
            if(flag)cout<<ans<<endl;
            else cout<<-1<<endl;
        }
    }

4 判断所给出的信息能否排序(拓补+并查集)

首先如果是= ,就将它们合并,后期这个连通分量就看成一个点。
else 对于每一次关系,记录到in(用来判断是否0个入度)数组和vector(用来保存孩子)里面。
在solve里边的队列中:
1.如果同时出现了两个及以上的点入度为0,说明信息不全。
2.如果队列结束cnt不为0,说明出现有向环,冲突。
**注意:**在合并=的时候,当他们不处于同一个连通分量才合并,因为合并
在这里插入图片描述

void solve(){
    queue<int> q;
    for(int i=1;i<=n;i++){
        if(in[i]==0&&fa[i]==i)q.push(i);//必须要找根节点,大于小于的信息都是以根节点来记录的
    }
    while(!q.empty()){
        int t =q.front();
        q.pop();
        cnt--;
        if(q.size())uncertain=1;//说明同时有多个入度为0的点,这样无法区分顺序
        for(int i=0;i<vec[t].size();i++){
            in[vec[t][i]]--;
            if(!in[vec[t][i]])q.push(vec[t][i]);
        }
    }
    if(cnt)conflict=1;

    if(conflict)cout<<"CONFLICT"<<endl;
    else if(uncertain)cout<<"UNCERTAIN"<<endl;
    else cout<<"OK"<<endl;
}
int main(){
    ios::sync_with_stdio(false);

    int T;
    int a,b;
    int m,k,q;
    char c;
    
    while(cin>>n>>m){
  
        init();
        cnt=n;
        for(int i=1;i<=m;i++){
            cin>>N[i].a>>N[i].c>>N[i].b;
            N[i].a++;   
            N[i].b++;
            if(N[i].c=='='){
                if(find(N[i].a)==find(N[i].b))continue;
                merge(N[i].a,N[i].b);
                cnt--;
            }
        }//先合并所有同类的点

        for(int i=1;i<=m;i++){
            a=find(N[i].a);
            b=find(N[i].b);
            if(N[i].c=='>'){
               in[a]++;
               vec[b].push_back(a);
            }else if(N[i].c=='<'){
               in[b]++;
               vec[a].push_back(b);
            }
        }
         solve();
    
    }

5 并查集判断小朋友牵手同构

两个图都是一样的操作:最开始先merge,对于每一个连通分量,把该连通分量是否为环,总共几个点,记录在根节点为下标的数组中。然后把所有的连通分量的头节点放入数组中并排序。
逐一比较两个数组,必须完全一样才是同构。
这题能这样的做的原因是: 题目中说每个小朋友只有两只手,所以如果成环一定是圆圈(不可能是局部环)。所以小朋友牵手只有两种情况,一种成链,一种成环。所以通过记录人数和是否成环这两个条件就足以判断是否同构。
在这里插入图片描述

void merge(int a,int b){
    int xa=find(a);
    int xb=find(b);
    if(xa!=xb){
        fa[xb]=xa;
        person[xa]+=person[xb];
    }
    else iscircle[xa]=1;
}
for(int v=1;v<=T;v++){
        init();
        cin>>n>>m;
        for(int i=1;i<=m;i++){
            cin>>a>>b;
            merge(a,b);
        }
        int cnt1=0;
        for(int i=1;i<=n;i++){
            if(fa[i]==i){
                G1[++cnt1].person=person[i];
                G1[cnt1].iscirle=iscircle[i];
            }
        }
        sort(G1+1,G1+1+cnt1,cmp);
        init();
        cin>>n>>m;
        for(int i=1;i<=m;i++){
            cin>>a>>b;
            merge(a,b);
        }
        int cnt2=0;
        for(int i=1;i<=n;i++){
            if(fa[i]==i){
                G2[++cnt2].person=person[i];
                G2[cnt2].iscirle=iscircle[i];
            }
        }
        sort(G2+1,G2+1+cnt2,cmp);
        int flag=1;
        if(cnt1!=cnt2)flag=0;
        if(flag){
            for(int i=1;i<=cnt1;i++){
                if(G1[i].person!=G2[i].person||G1[i].iscirle!=G2[i].iscirle)flag=0;
            }
        }
        cout<<"Case #"<<v<<": ";
        if(flag)cout<<"YES"<<endl;
        else cout<<"NO"<<endl;
    }

6

在这里插入图片描述
题意很恶心,如果有两个点a,b,a到b有很多路径。而T就是min{路径1的最长边,路径2的最长边。。。。}。
1.对所有边排序,每次把边拿出来merge直至小于给出的T。对于不同根的两个点,这条边一定是题给中的最长边,因为这是必经边。
2.对查询进行排序,从小到大可以只操作一次,这样可以省时间。
3.本人思索很久的是:出现环为什么直接return 0.根据题目的意思和别人的博客,成环的那条边显然大于所有边,但是为何不记录?答:题意中的how many kind of path 指的是有多少组点对。题意不清晰

#include<bits/stdc++.h>
using namespace std;
const int INF=10001;
int fa[INF+1];
int person[INF+1];
int iscircle[INF+1];
int answer[INF+1];
int n;
struct node{
   int a,b,c;
}N[INF*5];
struct ans{
    int val;
    int pos;
}A[INF];
int cmp(node a,node b){
    return a.c<b.c;
}
int cmp1(ans a,ans b){
    return a.val<b.val;
}
int find(int x){
    if(x==fa[x])return x;
    else return fa[x]=find(fa[x]);
}
void init(){
    for(int i=1;i<=INF;i++){
        fa[i]=i;
        person[i]=1;//表示连通分量人数
    }
}
int merge(int a,int b){
    int xa=find(a);
    int xb=find(b);
    if(xa==xb)return 0;//
    if(xa!=xb){
        fa[xb]=xa;
        int sum=person[xa]*person[xb];
        person[xa]+=person[xb];
        return sum;
    }
}
int main(){
    ios::sync_with_stdio(false);

    int T;
    int a,b;
    int m,k,q;
    char c;
    
    while(cin>>n>>m>>k){
        init();
        for(int i=1;i<=m;i++)
        cin>>N[i].a>>N[i].b>>N[i].c;
        sort(N+1,N+1+m,cmp);
        for(int i=1;i<=k;i++){
            cin>>A[i].val;
            A[i].pos=i;
        }
        sort(A+1,A+1+k,cmp1);
        int j=1;
        int sum=0;
        for(int i=1;i<=k;i++){
            //根据题意,不可能出现环,所以每条边都是新路径的必经路径
            //而又由于排序,这条路径一定是所谓的最长边,只要让最长边小于t就行
            while(j<=m&&N[j].c<=A[i].val){
                sum+=merge(N[j].a,N[j].b);
                j++;
            } 
            answer[A[i].pos]=sum;
            
        }
        for(int i=1;i<=k;i++)
        cout<<answer[i]<<endl;
    }
  
       
    
    system("pause");
    return 0;
}

  
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值