kuangbin专题之并查集

我本以为并查集是一个很简单的数据结构,刚开始看的时候就看明白了,后来我遇到了带权并查集…等我慢慢磨懂带权并查集之后,我以为并查集也就这样的难度了…后来我又遇到了离线操作…并查集优化搜索…反向建立并查集…当我把上面这些全写会后我又遇到了并查集+dp找方案数…我还是太年轻了…
-----------------------------------------------------------------
-----------------------------------------------------------------
本来不打算写这个专题.因为自己前一段时间就练过且寒假集训也有一套题…后来想想还是扎实一下基础再复习一下带权并查集就开了这个专题。。然后就被疯狂打脸,这个专题学到了很多并查集的好东西(sao cao zuo)
---------------------------------------------------------------------
专题地址
A - Wireless Network POJ - 2236
在这里插入图片描述
在这里插入图片描述
题意:给出通讯站的个数n和最短距离dis(只有距离不超过dis且均未被破坏的通讯站才可以互相通讯,具有传递性.ab,bc,等同于ac)
之后n行给出每个通讯站的坐标
然后就是多组输入处理到文件结尾,每行给出相应的操作
起初所有通讯站都是坏的
为O pos, 代表修好pos位置的通讯站
为S a,b代表询问a和b是否可以联系
简单题,初始化并查集每个根节点为自己,然后开个数组储存修好的通讯站,每当有一个通讯站被修好后,就放进数组内,且与修好的通讯站中相距距离小于dis都并在一起,查询时看ab根节点是否相同即可

const int maxn=1e3+5;
const int maxm=1e5+5;
int dad[maxn];
int seek(int x)
{
    if(x==dad[x])
        return x;
    return dad[x]=seek(dad[x]);
}
struct node
{
    double x,y;
}arr[maxn];
int good[maxn];
void mix(int a,int b)
{
    int fa=seek(a),fb=seek(b);
    dad[fa]=fb;
}
double get(int a,int b)
{
    return sqrt(pow(arr[a].x-arr[b].x,2)+pow(arr[a].y-arr[b].y,2));
}
void solve()
{
    int n=read();
    double d;scanf("%lf",&d);
    rep(i,1,n)
    {
        dad[i]=i;
        cin>>arr[i].x>>arr[i].y;
    }
    ms(good,0);
    char ope;
    int num=0;
    while(cin>>ope)
    {
        if(ope=='O')
        {
            int pos=read();
            good[++num]=pos;
            rep(i,1,num-1)
            {
                double dis=get(pos,good[i]);
                if(dis<=d)
                {
                    mix(good[i],pos);
                }
            }
        }
        else 
        {
            int a=read(),b=read();
            string ans="FAIL";
            if(seek(a)==seek(b))
            {
                ans="SUCCESS";
            }
            cout<<ans<<endl;
        }
    }
}

B - The Suspects POJ - 1611
在这里插入图片描述
在这里插入图片描述
多组输入n,m代表学生数目和小组数目0,0结尾
然后是m行 每行先给一个小组成员数目num,再输入num个数为成员编号
0号是传染源,和0号一个组的都可能会感染,和可能感染的人一个组也可能被感染,求可能被感染的人的数目(包括0)
很简单的并查集,把每个组都合在一起,然后遍历学生,如果这个学生和0号学生的根节点一样,就计数,最后输出数目即可

const int maxn=3e4+5;
const int maxm=1e5+5;
int dad[maxn];
int cup[maxn];
int seek(int x)
{
    if(x==dad[x])
        return x;
    return dad[x]=seek(dad[x]);
}
void mix(int a,int b)
{
    int fa=seek(a),fb=seek(b);
    dad[fa]=fb;
}
void solve()
{
    int n,m;
    while(~scanf("%d %d",&n,&m))
    {
        if(n==0&&m==0)
            break;
        rep(i,0,n-1)
        {
            dad[i]=i;
        }
        while(m--)
        {
            int num=read();
            rep(i,1,num)
            {
                cup[i]=read();
                if(i==1)continue;
                mix(cup[1],cup[i]);
            }
        }
        int ans=0;
        int pos=seek(0);
        rep(i,0,n-1)
        {
            ans+= seek(i)==pos ;
        }
        printf("%d\n",ans);
    }
}

C - How Many Tables HDU - 1213
在这里插入图片描述
出门聚餐,人们只和自己朋友圈的人坐在一起,只要有相同的朋友就算是同一个朋友圈的,求问共有多少个群体
这道题我认为是最简单最基础的并查集模板题了,直接统计根节点的数目

const int maxn=3e4+5;
const int maxm=1e5+5;
int dad[maxn];
int cup[maxn];
int seek(int x)
{
    if(x==dad[x])
        return x;
    return dad[x]=seek(dad[x]);
}
void mix(int a,int b)
{
    int fa=seek(a),fb=seek(b);
    dad[fa]=fb;
}
void solve()
{
    int n=read(),m=read();
    rep(i,1,n)
    {
        dad[i]=i;
    }
    while(m--)
    {
        int a=read(),b=read();
        mix(a,b);
    }
    int ans=0;
    rep(i,1,n)
    {
        ans+=seek(i)==i;
    }
    printf("%d\n",ans);
}

D - How Many Answers Are Wrong HDU - 3038
在这里插入图片描述
在这里插入图片描述
给出数的范围n和询问次数m
每次给出a,b,c代表从a到b的权值是c
求问有多少个与前面冲突的,没冲突的就算是正确的
这个是我写的第一道带权并查集,当初才开始写的时候边看题解马慢慢写的迷迷糊糊就过去了,现在又回来加强了一遍理解
我们可以用一个v数组代表每个数到自己根节点的距离,对于每一个给出的a,b,c我们使a–,这样得到两个节点a,b查询是否是同一个集合内的,是的话就看是否等于c,不等于就计数,不在同一个集合就合并并记录

const int maxn=2e5+5;
const int maxm=1e5+5;
int dad[maxn];
int val[maxn];
int seek(int x)
{
    if(x!=dad[x])
    {
        int tmp=dad[x];
        dad[x]=seek(dad[x]);
        val[x]+=val[tmp];
    }
    return dad[x];
}
void solve()
{
    int n,m;
    while(cin>>n>>m){
    rep(i,1,n)
    {
        dad[i]=i;
    }
    ms(val,0);
    int ans=0;
    while(m--)
    {
        int st=read(),ed=read(),tmp=read();
        int fst=seek(st-1);
        int fed=seek(ed);
        if(fst==fed)
        {
            if(val[ed]-val[st-1]!=tmp)
                ans++;
        }
        else 
        {
            dad[fed]=fst;
            val[fed]=val[st-1]+tmp-val[ed];
        }
    }
    printf("%d\n",ans);
    }
}

E - 食物链 POJ - 1182
在这里插入图片描述
emmmmm当初为了理解带权并查集,也去写这个题然后死活过不去,,现在一遍就过了。。果然进步了不少bksw
这道带权并查集和上面那个差不多,用val数组代表每个节点与根节点之间的关系,0代表同类,1代表可以吃根,2代表被根吃,注意相减的时候要+3再%3,不然可能会出现负数,
我觉得带权并查集和普通并查集的区别就是,原来的并查集,题目要合并就合并了,但是带权的就不一样了,内部之间需要有特定的关系,我们在合并的时候必须要维护内部的稳定
而带权并查集的难点我认为在于权值的设定。。。这个东西要多写题才能熟练

在这里插入代码片

wdnmd…vj突然崩溃了…代码和题面都在里面…
被迫停更…
在这里插入图片描述
回来了,继续更

const int maxn=5e4+5;
const int maxm=1e5+5;
int dad[maxn];
int val[maxn];
int seek(int x)
{
    if(x!=dad[x])
    {
        int tmp=dad[x];
        dad[x]=seek(dad[x]);
        val[x]=(val[x]+val[tmp])%3;
    }
    return dad[x];
}
void solve()
{
    int n=read(),m=read();
    rep(i,1,n)
    {
        dad[i]=i;
        val[i]=0;
    }
    ms(val,0);
    int ans=0;
    while(m--)
    {
        int d=read(),x=read(),y=read();
        if(x>n||y>n||(x==y&&d==2))
        {
            ans++;
            continue;
        }
        int fx=seek(x),fy=seek(y);
        if(fx!=fy)
        {
            int tmp= d==1 ? 0:1;
            dad[fx]=fy;
            val[fx]=(tmp+val[y]-val[x]+3)%3;
        }
        else 
        {
            if(d==1)
            {
                ans+= val[x]!=val[y];
            }
            else 
            {
                ans+=((val[y]+1)%3!=val[x]);
            }
        }
    }
    printf("%d\n",ans);
}

F - True Liars POJ - 1417
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
这个是这套题最难的一个了…我是看着题解慢慢写的…理解是理解了…但自己完全脱离出来动手还是有难度的…
首先是并查集分组,然后对于不同的组我们想象成一个01背包,求出组成p1个人的方案数,方案数大于1就不唯一输出no。反之就从小到大输出神族的人…我不明白为什么我写不等于1就是wa 改成大于1就ac…难道方案不存在也算???
讲不了,讲不了…直接放代码,以后有能力再补(主要是输出方案这一块太搞人心态了)

const int maxn=1e3+5;
const int maxm=3e2+5;
int n,p1,p2;
int dp[maxn][maxn];//前i个人组成j的方案数
int v[maxn],dad[maxn],cup[maxn][2],chose[maxn][maxn],num=0;
int ans[maxn][2];
int seek(int x){
    if(dad[x]==x)return x;

    int t=dad[x];
    dad[x]=seek(dad[x]);
    v[x]=(v[x]+v[t]+2)%2;
    return dad[x];
}
void init(){
    rep(i,0,p1+p2){
        dad[i]=i;
        v[i]=0;
    }
    ms(ans,0);
    ms(cup,0);
    ms(chose,0);
    ms(dp,0);
    num=0;
}
void mix(int a,int b,int d){
    int fa=seek(a),fb=seek(b);
    if(fa==fb)return ;
    dad[fa]=fb;
    v[fa]=(d+v[b]-v[a]+2)%2;
}
void solve(){
    while(~scanf("%d %d %d",&n,&p1,&p2)){
        if(n==0&&p1==0&&p2==0)break;
        init();
        rep(i,1,n){
            int a,b;
            char say[10];
            scanf("%d %d %s",&a,&b,say);
            //getchar();
            //de(a),de(b),de(say);
            int f= say[0]=='y' ? 0:1;
            mix(a,b,f);
        }
        map<int,int>mp;
        n=p1+p2;
        rep(i,1,n){
            int fa=seek(i);
           // de(i),de(fa),de(0);
            if(fa==i){
                mp[i]=++num;
            }
        }
        rep(i,1,n){
            int fa=seek(i);
            cup[mp[fa]][v[i]]++;
        }
        ms(dp,0);
        dp[0][0]=1;
        rep(i,1,num)
        {
            for(int j=p1;j>=1;j--)
            {
                if(j>=cup[i][0]&&dp[i-1][j-cup[i][0]]){
                    dp[i][j]+=dp[i-1][j-cup[i][0]];
                    chose[i][j]=cup[i][0];
                }
                if(j>=cup[i][1]&&dp[i-1][j-cup[i][1]]){
                    dp[i][j]+=dp[i-1][j-cup[i][1]];
                    chose[i][j]=cup[i][1];
                }
            }
        }
        //de(dp[num][p1]);
        //de(num);
        if(dp[num][p1]>1){
            puts("no");
        }
        else{
            int res=p1;
            for(int i=num;i>=1;i--){
                int pp=chose[i][res];
                if(pp==cup[i][0]){
                    ans[i][0]=1;
                }
                else{
                    ans[i][1]=1;
                }
                res-=pp;
            }
            rep(i,1,n){
                int fa=seek(i);
                if(ans[mp[fa]][v[i]]){
                    printf("%d\n",i);
                }
            }
            puts("end");
        }
    }
}

G - Supermarket POJ - 1456
在这里插入图片描述
在这里插入图片描述
这个就是我在开头提到的并查集查找优化贪心
题意: 给出物品数目n 以及每个物品的利润和销售截至日期,比如为day,那么你最晚最晚要在day-1天的时候开始卖,且一天只能卖一种物品,求出最大利润之和
我们可以很轻易的想到这是一个贪心,优先考虑就、利润大的物品,并把它们尽可能晚的卖出去,来不占用空间,我们记录下哪些天已经被安排上了,每一次找一个当前天数前最近的一个天数来卖出去,但是依靠遍历来查找天数太浪费时间了,我们可以用并查集来优化这一个过程
如果当前天数的根节点使它自己,就代表这一天没有被用过,然后就把它的根节点改为它的前一天,指向,这样你下一次查找到这一天时就会自动跳转到这一天的前一天,以此类推,加上路径压缩来优化并查集就OK了。我记得在cf做过类似的关于移动书的问题也是这个思路
先sort优先安排价值大的先处理,然后尽可能往后靠,再并查集,根节点为0代表不可卖出了

const int maxn=1e4+5;
const int maxm=1e5+5;
struct node
{
    int st,val;
}arr[maxn];
int dad[maxn];
int seek(int x)
{
    if(dad[x]==-1)
        return x;
    else 
        return dad[x]=seek(dad[x]);
}
bool cmp(node a,node b)
{
    if(a.val==b.val)
        return a.st>b.st;
    else
        return a.val>b.val;
}
void solve()
{
    int n;
    while(~scanf("%d",&n))
    {
        int ans=0;
        ms(dad,-1);
        ms(arr,0);
        rep(i,1,n)
        {
            arr[i].val=read();
            arr[i].st=read();
        }
        sort(arr+1,arr+n+1,cmp);
        rep(i,1,n)
        {
            int t=seek(arr[i].st);
            if(t>0)
            {
                ans+=arr[i].val;
                dad[t]=t-1;
            }
        }
        printf("%d\n",ans);
    }
}

H - Parity game POJ - 1733
在这里插入图片描述
给出范围n,询问次数m,然后m行询问代表这一个区域内1的个数是偶数还是奇数,
这题类似于上面那个区间之和的题目。。这个甚至更简单一点,一个性质的
权val数组代表这个点到根节点1的个数的奇偶情况
因为数太大,可以用map改一下映射的值来开数组写

const int maxn=5e3+5;
const int maxm=1e5+5;
int dad[maxn<<1];
int dis[maxn<<1];
map<int,int>mp;
int seek(int x)
{
    if(dad[x]==-1)
        return x;
    else 
    {
        int tmp=dad[x];
        dad[x]=seek(dad[x]);
        dis[x]=(dis[x]+dis[tmp])%2;
    }
    return dad[x];
}
int num;
map<int,int>::iterator it;
int insert(int x)
{
    it=mp.find(x);
    if(it==mp.end())
    {
        return mp[x]=++num;
    }
    return mp[x];
}
void solve()
{
    int n=read();
    ms(dad,-1);
    ms(dis,0);
    num=0;
    int m=read(),k=m;
    rep(i,1,m)
    {
        int a,b,f;string ans;
        cin>>a>>b>>ans;
        f= ans=="even" ? 0:1;
        if(k!=m)continue;
        a=insert(a-1),b=insert(b);
        //int fa=insert(a),fb=insert(b);
        //de(fa),de(fb),de(10000000000);
        int fa=seek(a),fb=seek(b);
        if(fa!=fb)
        {
            dad[fa]=fb;
            dis[fa]=(dis[b]+f-dis[a]+2)%2;
        }
        else 
        {
            if(dis[a]!=((f+dis[b])%2))
            {
                k=i-1;
            }
        }
    }
    printf("%d\n",k);
}

在这里插入图片描述
在这里插入图片描述
这也是一个维护距离的带权并查集,区别是这个要维护x轴和y轴两个方向的权值…查询的时候,如果两点根节点一致,则可以确定关系,输出曼哈顿距离,反之输出-1

const int maxn=1e4+5;
const int maxm=1e5+5;
struct node 
{
    int rt,dx,dy;
}dad[maxm];
struct nu
{
    int st,ed,dis;
    char dir;    
}build[maxm];
struct qu{
    int st,ed,time;
}query[maxm];
int n,m,k;
int seek(int x)
{
    if(dad[x].rt==x)
        return x;
    int t=dad[x].rt;
    dad[x].rt=seek(dad[x].rt);
    dad[x].dx+=dad[t].dx;
    dad[x].dy+=dad[t].dy;
    return dad[x].rt;
}
void mix(nu tmp)
{
    int a=tmp.st,b=tmp.ed;
    int fa=seek(a),fb=seek(b);
    //if(fa==fb)return ;
    dad[fb].rt=fa;
    int dx=0,dy=0;
    switch(tmp.dir)
    {
        case 'E': dx+=tmp.dis;break;
        case 'W': dx-=tmp.dis;break;

        case 'N': dy+=tmp.dis;break;
        case 'S': dy-=tmp.dis;break;
    }
    dad[fb].dx=dad[a].dx-dad[b].dx+dx;
    dad[fb].dy=dad[a].dy-dad[b].dy+dy;
}
void input()
{
    cin>>n>>m;
    rep(i,1,n){dad[i].rt=i,dad[i].dx=0,dad[i].dy=0;}
    rep(i,1,m)
    {
        cin>>build[i].st>>build[i].ed>>build[i].dis>>build[i].dir;
    }
    cin>>k;
    rep(i,1,k)
    {
        cin>>query[i].st>>query[i].ed>>query[i].time;
    }
    query[0].time=0;
}
void solve()
{
    input();
    int now=1;
    rep(i,1,k)
    {
        for(int j=now;j<=query[i].time;j++)
        {
            mix(build[j]);
        }
        int a=query[i].st,b=query[i].ed;
        int fa=seek(a),fb=seek(b);
        if(fa!=fb)puts("-1");
        else {
            printf("%d\n",abs(dad[a].dx-dad[b].dx)+abs(dad[a].dy-dad[b].dy));
        }
        now=query[i].time;
    }
}

其实输入是不用离线的…我写的丑了
在这里插入图片描述
虫子只能异性相交,维护一个集合,每次输入俩编号,代表两个虫子相交,即性别相异,求问是否存在同性恋的虫子
如果发现当前加入的虫子冲突了原有的,就代表有同性恋虫子,简单题

const int maxn=2e3+5;
const int maxm=1e5+5;
int dad[maxn],dis[maxn];
int seek(int x)
{
    if(dad[x]==-1)
        return x;
    else
    {
        int tmp=dad[x];
        dad[x]=seek(dad[x]);
        dis[x]=(dis[x]+dis[tmp])%2;
        return dad[x];
    }
}
void solve()
{
    int t=read();
    rep(cas,1,t)
    {
        int n=read(),m=read();
        ms(dad,-1),ms(dis,0);
        string str="No suspicious bugs found!";
        while(m--)
        {
            int a=read(),b=read();
            int fa=seek(a),fb=seek(b);
            if(fa!=fb)
            {
                dad[fa]=fb;
                dis[fa]=(dis[b]+1-dis[a]+2)%2;
            }
            else 
            {
                if((1+dis[b])%2!=dis[a])
                {
                    str="Suspicious bugs found!";
                }
            }
        }
        if(cas!=1)
        {
            cout<<endl;
        }
        cout<<"Scenario #"<<cas<<":"<<endl;
        cout<<str<<endl;
    }
}

K - Rochambeau POJ - 2912
在这里插入图片描述
寒假集训题
大意是除了裁判以外其他的人只能出 剪刀石头布其中一种,且不能更换,求问是否可以推断出谁是裁判,且输出至少多少轮后必定可以推出
维护一个集合,然后我们枚举每个可能为裁判的数,遇到这个数就跳过,如果剩下的回合可以保证没有冲突,就代表这个人可能是裁判并计数,如果有冲突就记录下冲突的轮数取最大的
最后查看裁判的数目是否为1,为1就代表可以推断出,且轮数为最大的冲突轮数,为0就代表不可能,大于1就代表不确定

const int maxm=2e3+5;
const int maxn=5e2+5;
int dad[maxn],val[maxn];
struct node 
{
    int l,rel,r;
}arr[maxm];
int change(char x)
{
    if(x=='=')
    {
        return 0;
    }
    else if(x=='<')
    {
        return 2;
    }
    else 
    {
        return 1;
    }
}
int seek(int x)
{
    if(dad[x]==x)
        return x;
    int tmp=dad[x];
    dad[x]=seek(dad[x]);
    val[x]=(val[x]+val[tmp]+3)%3;
    return dad[x];
}
void solve()
{
    int n,m;
    while(~scanf("%d %d",&n,&m))
    {
        rep(i,1,m)
        {
            char ch;
            scanf("%d%c%d",&arr[i].l,&ch,&arr[i].r);
            arr[i].rel=change(ch);
        }
        int pos=0,maxline=0,cnt=0;
        rep(i,0,n-1)
        {
            bool flag=true;
           // int tmp=-1;
            rep(j,0,n-1)
            {
                dad[j]=j;
                val[j]=0;
            }
            rep(j,1,m)
            {
                int l=arr[j].l,r=arr[j].r,rel=arr[j].rel;
                if(l==i||r==i)continue;
                int fl=seek(l),fr=seek(r);
                if(fl!=fr)
                {
                    dad[fl]=fr;
                    val[fl]=(rel+val[r]+3-val[l])%3;
                }
                else 
                {
                    if(val[l]!=((rel+val[r])%3))
                    {
                        flag=false;
                        //tmp=j;
                        maxline=max(maxline,j);
                        break;
                    }
                }
            }
            if(flag)
            {
                cnt++;
                pos=i;
            }
            if(cnt>1)break;
        }
        if(cnt>1)
        {
            puts("Can not determine");
        }
        else if(cnt==1)
        {
            printf("Player %d can be determined to be the judge after %d lines\n",pos,maxline);
        }
        else 
        {
            puts("Impossible");
        }
    }
}

打cf了,剩下三个明天补上去
L - Connections in Galaxy War ZOJ - 3261
在这里插入图片描述
给出星球数目n,下一行输入每个星球的能量,再输入空间隧道数目m,然后m行输入两个数ab代表a和b联通了,之后给出m个操作,query x代表 x能求救的星球的坐标,不存在就输出-1,多个就输出最小的,为destory x y就代表断开x,y
每个星球只能向和自己相连的且能量大于自己(最大)的星球求救
关于输出最小序号且能量要大于自己的这一个要求,我们在维护并查集的时候,就更新能量大的为根,且相同能量取小结点为根
如果这题要求的是大于等于自己,且不能为自己,我们就要自行修改一下,再储存一个权值num代表和根节点相同大的点的个数,并用val存下 相同大的最小的值
这题的难点是并查集只涉及了合并与查询,对于删除这个操作办不到
然后我们就可以学到一个新知识了,离线反向建立骚操作
我们先用map和pair标记下每一条边的建立,之后在破坏输入的时候再取消被破坏的边,然后我们反向查询,从最后一个询问开始查询,当遇到破坏的时候,就再把边连起来,这样就可以等效于 破坏后面的处于破坏状态,破坏前面的没有被破坏了

const int maxn=1e4+5;
const int maxm=1e5+5;
int ans[maxn*5];
int val[maxn];
int dad[maxn];
pii P1[maxn*2],P2[maxn*5];
string ope[maxn*5];
int seek(int x)
{
    if(dad[x]==x)
        return x;
    return dad[x]=seek(dad[x]);
}
void mix(int a,int b)
{
    int fa=seek(a),fb=seek(b);
    if(fa==fb)return ;
    if(val[fa]>val[fb])
    {
        dad[fb]=fa;
    }
    else if(val[fa]<val[fb])
    {
        dad[fa]=fb;
    }
    else 
    {
        if(fa<fb)
        {
            dad[fb]=fa;
        }
        else 
        {
            dad[fa]=fb;
        }
    }
}
void solve()
{
    int n;
    int num=0;
    while(~scanf("%d",&n))
    {
        rep(i,0,n-1)
        {
            dad[i]=i;
        }
        map<pii,int>mp;
        rep(i,0,n-1)
        {
            val[i]=read();
        }
        int m=read();
        rep(i,1,m)
        {
            P1[i].fi=read(),P1[i].se=read();
            if(P1[i].fi>P1[i].se)
            {
                swap(P1[i].fi,P1[i].se);
            }
            mp[P1[i]]=1;
        }
        int q=read();
        rep(i,1,q)
        {
            cin>>ope[i];
            if(ope[i][0]=='d')
            {
                P2[i].fi=read(),P2[i].se=read();
                if(P2[i].fi>P2[i].se)
                {
                    swap(P2[i].fi,P2[i].se);
                }
                mp[P2[i]]=0;
            }
            else 
            {
                P2[i].fi=read(),P2[i].se=-1;
            }
        }
        rep(i,1,m)
        {
            if(mp[P1[i]])
            {
                mix(P1[i].fi,P1[i].se);
            }
        }
        per(i,q,1)
        {
            if(ope[i][0]=='q')
            {
                int fa=seek(P2[i].fi);
                if(val[fa]<=val[P2[i].fi])
                {
                    ans[i]=-1;
                }
                else 
                {
                    ans[i]=fa;
                }
            }
            else 
            {
                int a=P2[i].fi,b=P2[i].se;
                mix(a,b);
            }
        }
        //int cas=0;
        if(num++)puts("");
        rep(i,1,q)
        {
            if(ope[i][0]=='q')
            {
                //if(cas++)printf(" ");
                printf("%d\n",ans[i]);
            }
        }
       // puts("");
    }
}

M - 小希的迷宫 HDU - 1272
在这里插入图片描述
这个其实就是要检查是否形成了环
我们在每次合并的时候,看两个节点根是否一样。一样的话就说明出现了环…
同时有个坑点是,不能存在两个根,意思是只能有一个集合,所以我们还要检查根的数目,可以通过标记数组vis或者用rank代表树的高度来代表是否有两个根

const int maxn=1e5+5;
const int maxm=1e5+5;
int dad[maxn],vis[maxn];
int seek(int x)
{
    if(dad[x]==x)
    {
        return x;
    }
    return dad[x]=seek(dad[x]);
}
void mix(int a,int b)
{
    dad[a]=b;
}
void solve()
{
    int a,b;
    while(~scanf("%d %d",&a,&b))
    {
        ms(vis,0);
        bool flag=true;
        if(a==-1&&b==-1)
        {
            break;
        }
        rep(i,1,100000)
        {
            dad[i]=i;
        }
        while(a!=0&&b!=0)
        {
            vis[a]=vis[b]=1;
            //mx=max(mx,max(a,b));
            int fa=seek(a),fb=seek(b);
            if(fa==fb)
            {
                flag=false;
            }
            else 
            {
                mix(fa,fb);
            }
            a=read(),b=read();
        }
        int cnt=0;
        for(int i=1;i<=100000;i++)
        {
            if(vis[i]&&dad[i]==i)
            {
                cnt++;
            }
            if(cnt>1)
            {
                flag=false;
                break;
            }
        }
        cout<<(flag ? "Yes":"No")<<endl;
    }
}

N - Is It A Tree? POJ - 1308
在这里插入图片描述
这个和上面那个小希的迷宫是一模一样的。。

const int maxn=1e5+5;
const int maxm=1e5+5;
int dad[maxn],vis[maxn];
int seek(int x)
{
    if(dad[x]==x)
    {
        return x;
    }
    return dad[x]=seek(dad[x]);
}
void mix(int a,int b)
{
    dad[a]=b;
}
void solve()
{
    int a,b;
    int cas=0;
    while(~scanf("%d %d",&a,&b))
    {
        ms(vis,0);
        bool flag=true;
        if(a==-1&&b==-1)
        {
            break;
        }
        rep(i,1,100000)
        {
            dad[i]=i;
        }
        while(a!=0&&b!=0)
        {
            vis[a]=vis[b]=1;
            //mx=max(mx,max(a,b));
            int fa=seek(a),fb=seek(b);
            if(fa==fb)
            {
                //de(a),de(b);
                flag=false;
            }
            else 
            {
                mix(fa,fb);
            }
            a=read(),b=read();
        }
        int cnt=0;
        for(int i=1;i<=100000;i++)
        {
            if(vis[i]&&dad[i]==i)
            {
                cnt++;
            }
            if(cnt>1)
            {
                flag=false;
                break;
            }
        }
        cout<<"Case "<<++cas<<" "<<(flag ? "is a tree.":"is not a tree.")<<endl;
    }
}

并查集入门就告一段落吧

猜你喜欢

转载自blog.csdn.net/leoxe/article/details/105491249
今日推荐