并查集 带权和拓展域

在这里插入图片描述
链接:https://ac.nowcoder.com/acm/contest/1031/C

输入

10
5
1 2 even
3 4 odd
5 6 even
1 6 even
7 10 odd

输出
3

一、带权并查集
因为本题传递多个关系,x==y y==z x==z,x!=y z!=y x==z ,x==y z!=y x!=z;
我们可以用异或和来表示,d【x】^d[y]=1表示不同,d[x] ^d[y]=0表示相同

#include <stdio.h>
#include <algorithm>
using namespace std;
struct node
{
    int l,r,ans;
}query[5005];
int d[10005],fa[10005],a[10005];
int n,m,tot;
int find(int x)
{
    if(x==fa[x])return x;
    int root=find(fa[x]);d[x]^=d[fa[x]];                   //不能用之前的模板,因为d【x】要从下往上逐步更新
    return fa[x]=root;
}
void ish()               //离散化
{
    char str[5];
    for(int i=1;i<=m;i++)
    {
        scanf("%d%d%s",&query[i].l,&query[i].r,str);
        query[i].ans=(str[0]=='o'?1:0);
        a[++tot]=query[i].l-1,a[++tot]=query[i].r;
    }
    sort(a+1,a+1+tot);
    n=unique(a+1,a+1+tot)-a-1;
}
int q(int x)                   //查找离散化后元素的位置,也就是值
{
    return lower_bound(a+1,a+1+n,x)-a;
}
int main()
{
    scanf("%d%d",&n,&m);
    ish();
    for(int i=1;i<=n;i++)fa[i]=i;
    for(int i=1;i<=m;i++)
    {
        int x=q(query[i].l-1),y=q(query[i].r);
        int p=find(x),q=find(y);
        if(p!=q)fa[p]=q,d[p]=d[x]^d[y]^query[i].ans;  //如果不在同一个集合,路径x~y 
        else                                          //ans=d[x] ^d[y] ^d[p] ,所有d[p]=d[x]^d[y]^ans
        {
            if(d[x]^d[y]!=query[i].ans)                    //在同一个集合
            {
                printf("%d\n",i-1);
                return 0;
            }
        }
    }
    printf("%d",m);
}

拓展域
将x分成 x_odd x_even
y分成 y_odd y_even
如果相等 x_odd y_odd 合并 y_even x_even 合并
不相等 x_odd y_even 合并 x_even y_odd 合并
这样关系就被维护了

#include <stdio.h>
#include <algorithm>
using namespace std;
struct node
{
    int l,r,ans;
}query[5005];
int fa[20005],a[10005];
int n,m,tot;
int find(int x)
{
    return x==fa[x]?x:fa[x]=find(fa[x]);
}
void ish()
{
    char str[5];
    for(int i=1;i<=m;i++)
    {
        scanf("%d%d%s",&query[i].l,&query[i].r,&str);
        query[i].ans=str[0]=='o'?1:0;
        a[++tot]=query[i].l-1,a[++tot]=query[i].r;
    }
    sort(a+1,a+1+tot);
    n=unique(a+1,a+1+tot)-a-1;
}
int q(int x)
{
    return lower_bound(a+1,a+1+n,x)-a;
}
int main()
{
    scanf("%d%d",&n,&m);
    ish();
    for(int i=1;i<=2*n;i++)fa[i]=i;
    for(int i=1;i<=m;i++)
    {
        int x_odd=q(query[i].l-1),y_odd=q(query[i].r);
        int x_even=x_odd+n,y_even=y_odd+n;
        if(query[i].ans)                     //相同 
        {
            if(find(x_odd)==find(y_even))             //矛盾
            {
                printf("%d",i-1);
                return 0;
            }
            fa[find(x_odd)]=find(y_odd);
            fa[find(x_even)]=find(y_even);
        }
        else              //不同
        {
            if(find(x_odd)==find(y_odd))          //矛盾        
            {
                printf("%d",i-1);
                return 0;
            }
            fa[find(x_odd)]=find(y_even);
            fa[find(x_even)]=find(y_odd);
        }
    }
    printf("%d",m);
}

https://codeforces.com/contest/1263/problem/D
题意
two passwords a and b are equivalent if there is a letter, that exists in both a and b;
two passwords a and b are equivalent if there is a password c from the list, which is equivalent to both a and b.
即如果 有一个字母和别人相等 这两个属于同一个集合abcd……xyz包括所有集合

/*
这题难点就是如何将a b ab他们属于同一个集合 ,且计数为1
*/ 
#include <stdio.h>
int fa[28],a[28],n,cnt;
char s[55];
int find(int x)
{
    return x==fa[x]?x:fa[x]=find(fa[x]);
}
int main()
{
    scanf("%d",&n);
    for(int i=1;i<=26;i++)fa[i]=i;
    while(n--)
    {
        scanf("%s",s);
        for(int i=0;s[i];i++)a[s[i]-'a'+1]=1;   //出现字母,即不合并有多少集合 
        for(int i=1;s[i];i++)                   //cnt =合并的次数
        {
            int p=find(s[i]-'a'+1),q=find(s[i-1]-'a'+1);
            if(p!=q)fa[p]=q,cnt++;      //出现新的字母,并放入同一个集合 
        }                               //某个字符串如果与之前的都不交叉,则 
    }                                   //独立出来 
	for(int i=1;i<=26;i++)cnt-=a[i];    //独立出来的个数 =不合并-合并的次数 
        printf("%d",-cnt);
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值