并查集--2018 UESTC Training for Data Structures J K L

J-老头马桶枪!点击打开链接

题意:有三种生物像剪刀石头布一样相互克制 先给出一系列表述表示x和y是同类或x克制y 找出第一个和前面矛盾的表述 表述的语句数量小于等于10W

这题和poj1182 食物链是一样的 都是并查集进阶的基础题 这里有两种做法 第一种比较好理解 就是虽然不知道某个x是什么位置 但是可以设x x+C 和x+2*C表示他在3个状态的情况 如果x和y相同就分别合并x y,x+C,y+C,X+2*C Y+2*C,否则合并X Y+C,X+C,Y+2*C,X+2*C,Y 

AC代码:

#include <set>
#include <map>
#include <list>
#include <deque>
#include <cmath>
#include <queue>
#include <stack>
#include <bitset>
#include <vector>
#include <string>
#include <cstdio>
#include <cstdlib>
#include <iomanip>
#include <cstring>
#include <fstream>
#include <iostream>
#include <algorithm>
#pragma comment(linker, "/STACK:1024000000,1024000000")
using namespace std;

const int maxn=500005;
int f[maxn];

int findset(int x)
{
    return f[x]==x?x:f[x]=findset(f[x]);
}
void unionset(int x,int y)
{
    f[x]=y;
}
int main()
{
    int n,m;
    scanf("%d%d",&n,&m);
    for(int i=1; i<=n*3+5; i++)
    {
        f[i]=i;
    }
    int op,x,y,ans=-1;
    for(int i=0; i<m; i++)
    {
        scanf("%d%d%d",&op,&x,&y);
        if(ans==-1)
        {
            int fx1,fx2,fx3,fy1,fy2,fy3;
            fx1=findset(x);
            fx2=findset(x+n);
            fx3=findset(x+2*n);
            fy1=findset(y);
            fy2=findset(y+n);
            fy3=findset(y+2*n);
            if(op==1)
            {
                if(fx1!=fy2&&fx2!=fy3&&fx3!=fy1&&fy1!=fx2&&fy2!=fx3&&fy3!=fx1)
                {
                    unionset(fx1,fy1);
                    unionset(fx2,fy2);
                    unionset(fx3,fy3);
                }
                else ans=(i%3)+1;
            }
            else
            {
                if(fx1!=fy1&&fx2!=fy2&&fx3!=fy3&&fy1!=fx2&&fy2!=fx3&&fy3!=fx1)
                {
                    unionset(fx1,fy2);
                    unionset(fx2,fy3);
                    unionset(fx3,fy1);
                }
                else ans=(i%3)+1;
            }
        }
    }
    printf("%d\n",ans);
    return 0;
}

还有一种方法就是带权并查集+偏移量 这种相对比较不好理解 但是这种方法非常好用;

就是用一个结构体(或者两个数组也行)来存储一个节点的信息 信息包括他的父节点和他和父节点的关系 0代表他和父节点一样 1代表他被父节点克制 2代表他克制父节点 然后每次合并 那么这个时候合并就出现了一个问题 比如X和RX的关系是0 和RX和RRX的关系是1 那么当我要把X移到RRX的直接子节点是X和RRX的关系要随之变化

一开始我想着的是枚举所有情况 发现规律 类似于真值表那样 后来看网上博客说这种关系的改变恰好是符合向量运算的 打个例子:

RRX->X=RRX->RX+RX->X

有了这个法则之后这道题就可以很好的做了 注意向量有时要取反 比如:

X->Y=X->RX+RX->RY+RY->Y 这里的X->RX是要对Rx->X取反情况的

AC代码:

#include <set>
#include <map>
#include <list>
#include <deque>
#include <cmath>
#include <queue>
#include <stack>
#include <bitset>
#include <vector>
#include <string>
#include <cstdio>
#include <cstdlib>
#include <iomanip>
#include <cstring>
#include <fstream>
#include <iostream>
#include <algorithm>
#pragma comment(linker, "/STACK:1024000000,1024000000")
using namespace std;

const int maxn=200005;

struct node
{
    int fa;
    int rel;
};
node a[maxn];

int findset(int x)
{
    if(x==a[x].fa) return x;
    int pre=a[x].fa;
    a[x].fa=findset(pre);
    a[x].rel=(a[x].rel+a[pre].rel)%3;
    return a[x].fa;
}
int main()
{
    int n,m;
    scanf("%d%d",&n,&m);
    for(int i=1; i<=n; i++)
    {
        a[i].fa=i;
        a[i].rel=0;
    }
    int ans=-1,op,x,y;
    for(int i=0; i<m; i++)
    {
        scanf("%d%d%d",&op,&x,&y);
        if(ans==-1)
        {
            int fx=findset(x);
            int fy=findset(y);
            if(fx!=fy)
            {
                a[fy].fa=fx;
                a[fy].rel=(a[x].rel+(op-1)+(3-a[y].rel))%3;
            }
            else
            {
                if(op==1)
                {
                    if(a[x].rel!=a[y].rel)
                        ans=(i%3)+1;
                }
                else
                {
                    if((3-a[x].rel+a[y].rel)%3!=op-1)
                        ans=(i%3)+1;
                }
            }
        }
    }
    printf("%d\n",ans);
    return 0;
}

接下来就是比较常规的一道题:

K-爱吃瓜的伊卡洛斯:

大意有两种瓜 每次告诉你其中某两瓜是否相同 然后突然给一个询问 问X瓜和Y瓜是否相同 无法确定的话输出3:

这题用上面两种方法都行 这里采用带权并查集:

#include <set>
#include <map>
#include <list>
#include <deque>
#include <cmath>
#include <queue>
#include <stack>
#include <bitset>
#include <vector>
#include <string>
#include <cstdio>
#include <cstdlib>
#include <iomanip>
#include <cstring>
#include <fstream>
#include <iostream>
#include <algorithm>
#pragma comment(linker, "/STACK:1024000000,1024000000")
using namespace std;

const int maxn=100005;
struct node
{
    int fa;
    int rel;
};
node a[maxn];

int findset(int x)
{
    if(a[x].fa==x) return x;
    int pre=a[x].fa;
    a[x].fa=findset(pre);
    a[x].rel=(a[x].rel+a[pre].rel)%2;
    return a[x].fa;
}
int main()
{
    int n,m;
    scanf("%d%d",&n,&m);
    for(int i=1;i<=n;i++)
    {
        a[i].fa=i;
        a[i].rel=0;
    }
    char op;
    int x,y,tp,fx,fy;
    for(int i=0;i<m;i++)
    {
        cin>>op;
        if(op=='A')
        {
            scanf("%d%d%d",&x,&y,&tp);
            fx=findset(x);
            fy=findset(y);
            if(fx!=fy)
            {
                a[fy].fa=fx;
                a[fy].rel=(a[x].rel+(tp-1)+(2-a[y].rel)+100)%2;
            }
        }
        else
        {
            scanf("%d%d",&x,&y);
            fx=findset(x);
            fy=findset(y);
            if(fx==fy)
            {
                printf("%d\n",(-a[x].rel+a[y].rel+100)%2+1);
            }
            else
            {
                printf("3\n");
            }
        }
    }
    return 0;
}

这题虽然不难 但是有几个地方要注意 比如在向量运算中要注意加一个相对大点的偶数取模 不然他可能变成负数 还有要注意在合并的过程中对于两瓜是同一集合的时候我没有处理 之所以这样做是因为当他们都属于同一父节点和他们与父节点的关系确定的时候 他们之间的关系已经确定了(以为只有两种瓜) 这点并不适用与所有情况 比如接下这题:

L-爱吃瓜的伊卡洛斯(2):

题意和刚才差不多 只不过这里的瓜有无数种:

如果有无数种瓜的话直接用带权并查集就很难做了 因为就如同我上题讲的但两个节点都属于同一个父节点的时候 由于瓜有无数种 这两个节点的关系是不确定 假设当前我要改变这两个点的关系的话就很难找到一个通用的方法来改变这两个节点和父节点的偏移量从而使者两个节点的偏移量得到对应的改变 所以这里采用set+并查集+启发式的方法来做:

简单点说由于STL中set的底层实现是一棵红黑树 查询是log2级别 所以我们可以用set数组来表示每个节点 每次如果有不同的话就在x的set中加入y 在y的set中加入x 如果x和y相同就合并x和y 这里注意用启发式合并 把小的集合合并到大的集合中 大概估算一下 应该不超时

AC代码:

#include <set>
#include <map>
#include <list>
#include <deque>
#include <cmath>
#include <queue>
#include <stack>
#include <bitset>
#include <vector>
#include <string>
#include <cstdio>
#include <cstdlib>
#include <iomanip>
#include <cstring>
#include <fstream>
#include <iostream>
#include <algorithm>
#pragma comment(linker, "/STACK:1024000000,1024000000")
using namespace std;

const int maxn=100005;
set<int>s[maxn];
int f[maxn];

int findset(int x)
{
    return x==f[x]?x:f[x]=findset(f[x]);
}
void unionset(int x,int y)
{
    int fx,fy;
    fx=findset(x);
    fy=findset(y);
    if(s[fx].size()<s[fy].size()) swap(fx,fy);
    f[fy]=fx;
    set<int>::iterator it;
    for(it=s[fy].begin();it!=s[fy].end();it++)
    {
        s[fx].insert((*it));
    }
}
int main()
{
    int n,m;
    scanf("%d%d",&n,&m);
    for(int i=1;i<=n;i++)
    {
        f[i]=i;
        s[i].clear();
    }
    while(m--)
    {
        char op;
        int x,y,fx,fy,tp;
        cin>>op;
        if(op=='A')
        {
            scanf("%d%d%d",&x,&y,&tp);
            if(tp==1)
            {
                unionset(x,y);
            }
            else
            {
                fx=findset(x);
                fy=findset(y);
                s[fx].insert(fy);
                s[fy].insert(fx);
            }
        }
        else
        {
            scanf("%d%d",&x,&y);
            fx=findset(x);
            fy=findset(y);
            if(s[fx].find(fy)!=s[fx].end()||s[fy].find(fx)!=s[fy].end())
            {
                printf("2\n");
            }
            else if(fx==fy)
            {
                printf("1\n");
            }
            else
            {
                printf("3\n");
            }
        }
    }
    return 0;
}

还是太菜了 希望可以快点提升吧。。。

猜你喜欢

转载自blog.csdn.net/weixin_39302444/article/details/80565990