kuangbin专题五 并查集从入门到熟练4题

Posted zhenghanghu

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了kuangbin专题五 并查集从入门到熟练4题相关的知识,希望对你有一定的参考价值。

POJ 1611 The Suspects

基础并查集,由于需要知道数量维护一个total就可以了

技术分享图片
#include<iostream>
#include<cstdio>
#include<cstring>
#include<cmath>
#include<queue>
#include<string>
#include<stack>
#include<fstream>
#include<map>
#include<iomanip> 
#include<algorithm>
#include<vector>
#define inf 2e9
#define maxnode 200000
#define ll long long
#define lowbit(x) (x&(-x))
const int mod = 10007;
const int maxn = 3e4 + 10;
int dx[4]={0,0,1,-1};
int dy[4]={1,-1,0,0};
using namespace std;

int par[maxn],total[maxn];//total[i]只对root有效,代表这个关系群的人数总数量 

int get_root(int a){
    if( par[a]==a ) return a;
    return par[a]=get_root( par[a] );
}

void merge(int a,int b){
    int root_a=get_root(a);
    int root_b=get_root(b);
    if( root_a==root_b ) return;
    par[ root_a ] = root_b;
    total[root_b]+=total[root_a];
    
}

bool query(int a,int b){
    return get_root(a)==get_root(b);
}

int main(){
    
    while(1){
        int n,m; scanf("%d%d",&n,&m);
        if(n==0 && m==0) break;
        for(int i=1;i<=n;i++) par[i]=i;
        for(int i=1;i<=n;i++) total[i]=1;
        
        for(int i=1;i<=m;i++){
            int k; scanf("%d",&k);
            int start; scanf("%d",&start); start++;
            for(int i=1;i<k;i++){
                int x; scanf("%d",&x); x++;
                merge(start,x);
            }
        }
        
        printf("%d
",total[get_root(1)] );
    }
    
    return 0;
}
View Code

 

HDU 3038 How Many Answers Are Wrong

网上blog普遍都用向量的角度去理解,但我一直不懂并查集是怎么跟向量扯上关系的?

其实其本质是逻辑,可以从偏移量的角度出发去理解。

 

进阶并查集,套路是维护一个数组a[i]代表i与其祖先的某种关系(其实是与父节点的关系,经过路径压缩普遍变成与祖先的关系)

技术分享图片
#include<bits/stdc++.h>
#define inf 2e9
#define maxnode 200000
#define ll long long
#define lowbit(x) (x&(-x))
const int mod = 10007;
const int maxn = 2e5 + 10;
int dx[4]={0,0,1,-1};
int dy[4]={1,-1,0,0};
using namespace std;

int sum[maxn],par[maxn];
int find_root(int a){
    if( par[a]==a ) return par[a];
    int root = find_root(par[a]);
    sum[a]+=sum[ par[a] ];//得先改sum
    par[a]=root;
    return root;
}

int main(){

    //freopen("1.in","r",stdin);
     int n,m; 
     while( scanf("%d%d",&n,&m)!=EOF ){
         int cnt=0;
         for(int i=0;i<=n;i++){
             par[i]=i;
             sum[i]=0;
         }

         for(int i=1;i<=m;i++){
             int a,b,v; scanf("%d%d%d",&a,&b,&v);
             a--;
             int roota=find_root(a);
             int rootb=find_root(b);
             if( roota==rootb ){
                 if( v!=sum[a]-sum[b] ) cnt++;
             }
             else{
                 par[roota]=rootb;
                 sum[roota]=sum[b]-sum[a]+v;
             }
         }

         printf("%d
",cnt);
     }

    return 0;
}
View Code

 

POJ 1182 食物链

赤裸裸的逻辑,比上一题简单

技术分享图片
#include<iostream>
#include<cstdio>
#include<cstring>
#include<cmath>
#include<queue>
#include<string>
#include<stack>
#include<fstream>
#include<map>
#include<iomanip> 
#include<algorithm>
#include<vector>
#define inf 2e9
#define maxnode 200000
#define ll long long
#define lowbit(x) (x&(-x))
const int mod = 10007;
const int maxn = 5e4 + 10;
int dx[4]={0,0,1,-1};
int dy[4]={1,-1,0,0};
using namespace std;

int par[maxn],relation[maxn];
//relation[i]==0 代表 i跟par[i]是同类
//relation[i]==1 代表 i吃par[i] 
//relation[i]==2 代表 i被par[i]吃 

int find_root(int a){//会顺手把par改掉,所以也要顺手把relation改掉 
    if( par[a]==a ) return par[a];
    int root=find_root(par[a]);
    relation[a] = (relation[ par[a] ]+relation[a])%3;  //a与root的关系,是par与root的关系加上a与par的关系 
    return par[a]=root;
}

void merge(int a,int b,int relat){//relat是 a->b是relat,b->a就不是了 
    int roota=find_root(a);
    int rootb=find_root(b);
    if( roota==rootb ) return;
    par[rootb]=roota;    
    relation[rootb]=3-( (relat+relation[b]-relation[a]+3)%3);
}

int find_relation(int a){
    if( par[a]==a ) return 0;
    return (relation[a]+find_relation(par[a]))%3;
}

int main(){
    int n,k; scanf("%d%d",&n,&k);
    for(int i=1;i<=n;i++) par[i]=i;
    
    int cnt=0;
    for(int i=1;i<=k;i++){
        int d,x,y; scanf("%d%d%d",&d,&x,&y);
        if( d==2 && x==y ) cnt++;
        else if( x>n || y>n ) cnt++;
        else{
            if( d==1 ){
                int rx=find_root(x);
                int ry=find_root(y);
                if( rx!=ry ) merge(x,y,0);
                else{
                    if( find_relation(x)==find_relation(y) ) continue;
                    else cnt++;
                }
            }
            else{
                int rx=find_root(x);
                int ry=find_root(y);
                if( rx!=ry ) merge(x,y,1);
                else{
                    if( (1+find_relation(y)-find_relation(x)+3)%3==0 ) continue;
                    else cnt++;
                }
            }
        }
    }
    printf("%d
",cnt);

    
    return 0;
}
View Code

 

POJ 1417 True Liars

背包+进阶并查集

明明想到了最后一步,但没能看出背包模型

技术分享图片
//#include<bits/stdc++.h>
#include<stdio.h>
#include<iostream>
#include<vector>
#include<cstring>
#include<algorithm>

#define inf 2e9
#define maxnode 200000
#define ll long long
#define lowbit(x) (x&(-x))
const int mod = 10007;
const int maxn = 1e3 + 10;
int dx[4]={0,0,1,-1};
int dy[4]={1,-1,0,0};
using namespace std;

int par[maxn],relation[maxn];//relation[i]是i与祖先的关系
                             //0代表一样,1代表不一样


int find_root(int a){
    if( par[a]==a ) return par[a];
    int root = find_root(par[a]);
    relation[a] += relation[ par[a] ];//得先改sum
    relation[a]%=2;

    par[a]=root;
    return root;
}

int a[maxn][2];//a[i][0]代表第i个大集合中有多少个在一个集合,a[i][1]代表第i个大集合中有多少个在另一个集合
int used[maxn];
vector<int> b[maxn][2];//代表i大集合里的0所代表小集合里有谁

int dp[maxn][maxn],pre[maxn][maxn];//dp[i][j]代表前i个大集合凑出j个数的方案,答案是dp[cnt][p1]
                                    //pre[i][j]记录第i个大集合选了0还是1

int main(){

    //freopen("1.in","r",stdin);
     int n,p1,p2,relate; 
     while(1){
         scanf("%d%d%d",&n,&p1,&p2);
         if( n==0 && p1==0 && p2==0 ) break;

         for(int i=1;i<=p1+p2;i++){
             par[i]=i;
             relation[i]=0;
         }
        
         for(int i=1;i<=n;i++){
             int x,y; scanf("%d%d",&x,&y);
             char s[10]; scanf("%s",s);
             
             if( s[0]==n ) relate=1;
             else relate=0;

             int roota=find_root(x);
             int rootb=find_root(y);
             
             par[roota]=rootb;
             relation[roota]=(relation[y]-relation[x]+relate+2)%2;
             //cout<<"!!! "<<x<<" "<<y<<" "<<s<<" "<<relate<<endl;
             //cout<<x<<" "<<roota<<endl<<y<<" "<<rootb<<endl;
         }
         //现在分成了很多集合,每个集合又分成两个子集合,代表好人和坏人

         memset(used,0,sizeof(used));
         memset(a,0,sizeof(a));
         for(int i=1;i<=1000;i++) b[i][0].clear(),b[i][1].clear();

         int cnt=0;
         for(int i=1;i<=p1+p2;i++)
             if( !used[i] ){
                 cnt++;
                 int tmp=find_root(i);
                 for(int j=1;j<=p1+p2;j++)//遍历与i在一个大集合里的元素
                     if( tmp==find_root(j) ){
                         used[j]=1;//标记j为用过,这样大集合不会重复
                         a[cnt][ relation[j] ]++;
                         b[cnt][ relation[j] ].push_back(j);
                     }
                 
             }
         /*
         for(int i=1;i<=cnt;i++){
             cout<<"1"<<endl;
             for(int j=0;j<b[i][1].size();j++) cout<<b[i][1][j]<<" "; cout<<endl;

             cout<<"0"<<endl;
             for(int j=0;j<b[i][0].size();j++) cout<<b[i][0][j]<<" "; cout<<endl;
             cout<<i<<" "<<a[i][0]<<" "<<a[i][1]<<endl;
         }*/
         //开始dp
         memset(dp,0,sizeof(dp));
         memset(pre,0,sizeof(pre));
         dp[0][0]=1;

         for(int i=1;i<=cnt;i++){
             for(int j=0;j<=p1;j++){
                 if( j>=a[i][0] ) dp[i][j]+=dp[i-1][ j-a[i][0] ];
                 if( j>=a[i][1] ) dp[i][j]+=dp[i-1][ j-a[i][1] ];

                 if( dp[i][j]==1 ){
                     if( j>=a[i][0] && dp[i-1][j-a[i][0] ]==1 ) pre[i][j]=0;
                     else pre[i][j]=1;
                 }

         //        cout<<"!!! "<<i<<" "<<j<<" "<<dp[i][j]<<" "<<pre[i][j]<<endl;
             }
         }

         if( dp[cnt][p1]==1 ){
             vector<int> ans; ans.clear();
             int id=cnt,p=p1;
             while( id ){
                 int choice=pre[id][p];
                 //cout<<id<<" "<<p<<endl;
                 //cout<<"??? "<<choice<<endl;
                 if( choice==1 ) for(int i=0;i<b[id][1].size();i++) ans.push_back( b[id][1][i] ); 
                 else for(int i=0;i<b[id][0].size();i++) ans.push_back( b[id][0][i] );
                 p-=a[id][choice]; id--; 
             }             
             sort(ans.begin(),ans.end());
             for(int i=0;i<ans.size();i++) printf("%d
",ans[i]);
             printf("end
");
         }
         else{
             printf("no
");
         }
     }

    return 0;
}
View Code

 

以上是关于kuangbin专题五 并查集从入门到熟练4题的主要内容,如果未能解决你的问题,请参考以下文章

kuangbin专题六 最小生成树从入门到熟练5题

kuangbin专题十二 基础DP1从入门到熟练9+1题

kuangbin专题四 最短路练习从入门到熟练

kuangbin专题并查集

并查集---------kuangbin带你飞合集

❤️数据结构入门❤️(2 - 5)- 并查集