hdu 3234 Exclusive-OR 题解(并查集,思维)

博客介绍了如何使用并查集解决HDU 3234 Exclusive-OR问题,包括题意解析、数据结构、路径压缩、合并操作以及如何利用并查集处理题目中的异或操作。通过路径压缩实现O(1)复杂度更新Xor[i],合并操作时判断和处理矛盾,最后展示了如何应用并查集解决实际问题。

该死的期末复习终于结束了。。。

暑 假 来 了 \color{#ff0000}{暑假来了} !!!

所以我就珂以非常开心的写博客了。

原题链接:
hdu

题意简述

多组数据。你有一个没有确定的数列。有一些操作,格式:
I p v p p p个是 v v v(如果某个位置有很多个值,就是矛盾的)
I p q v(没错也是I,所以要用sscanf判一下空格数量)第 p p p个异或第 q q q个为 v v v(这个异或值也可能会有矛盾)
Q k p1 p2 ... pk p 1 , p 2... p k p1,p2...pk p1,p2...pk的异或和(如果无法确定输出I don't know.
处理以上操作时,如果有矛盾的,输出
The first i facts are conflicting. i i i为第一个矛盾的操作编号),然后忽略这组数据以下的赋值操作和询问操作。

数据

输入

多组数据。以0 0结束。
每组数据:

n m
//数列长度,操作个数(n<=20000,m<=40000)
operation
//一个操作,格式如上
输出
Case #i:
ans
ans
...
ans
//(ans=一个整数,I dont't know或者The first i facts are conflicting.)
样例

输入

2 6
I 0 1 3
Q 1 0
Q 2 1 0
I 0 2
Q 1 1
Q 1 0
3 3
I 0 1 6
I 0 2 2
Q 2 1 2
2 4
I 0 1 7
Q 2 0 1
I 0 1 8
Q 2 0 1
0 0

输出

Case 1:
I don't know.
3
1
2
Case 2:
4
Case 3:
7
The first 2 facts are conflicting.

思路

(注:题目中是 0 0 0编号,我就被掰弯了,也是 0 0 0编号。)
1 1 1编号了那么久。。。)

这个题。。。没做过的话,真的不会想到用并查集做。。。

我们维护一个并查集,带权,其中权值用 X o r Xor Xor数组表示, X o r [ i ] Xor[i] Xor[i] i i i i i i父亲的异或值。
那么,我们不是要资瓷路径压缩的么。。。怎么压缩捏。。。

part1. 找祖先(Find函数),带路径压缩

f a [ i ] fa[i] fa[i] i i i的父亲, a a a是那个要确定的序列, ⊕ \oplus 是异或
我们只要能把 X o r [ i ] Xor[i] Xor[i] O ( 1 ) O(1) O(1)的复杂度从原本的
a [ i ] ⊕ a [ f a [ i ] ] a[i] \oplus a[fa[i]] a[i]a[fa[i]]
变成
a [ i ] ⊕ a [ f a [ f a [ i ] ] ] a[i] \oplus a[fa[fa[i]]] a[i]a[fa[fa[i]]]
我们会发现, X o r [ f a [ i ] ] = a [ f a [ i ] ] ⊕ a [ f a [ f a [ i ] ] ] Xor[fa[i]]=a[fa[i]]\oplus a[fa[fa[i]]] Xor[fa[i]]=a[fa[i]]a[fa[fa[i]]]
我们用 X o r [ i ] Xor[i] Xor[i]异或 X o r [ f a [ i ] ] Xor[fa[i]] Xor[fa[i]],那么 X o r [ f a [ i ] ] Xor[fa[i]] Xor[fa[i]]就被异或了两次,也就是没了(众所周知异或同一个数两次相当于没异或)。所以,我们只要 X o r [ i ] ⊕ = X o r [ f a [ i ] ] Xor[i]\oplus=Xor[fa[i]] Xor[i]=Xor[fa[i]],就珂以一步一步把 X o r [ i ] Xor[i] Xor[i]设置为 a [ i ] ⊕ a [ a n c e s t o r ] a[i]\oplus a[ancestor] a[i]a[ancestor]了(其中 a n c e s t o r ancestor ancestor是祖先),就实现了路径压缩。
写出代码:

int Find(int x)
{
    if (x!=Father[x])
    {
        int fa=Father[x];
        Father[x]=Find(Father[x]);
        Xor[x]^=Xor[fa];
    }
    return Father[x];
}//说了这么多,代码很水,注意不要写挂即可。
此时,路径压缩完了, X o r [ ] Xor[] Xor[]的定义变为: X o r [ i ] Xor[i] Xor[i] i i i i i i祖先的异或值。注意。

那么。。。合并捏?

part2. 合并(Merge函数)

假设我们要设置 a [ x ] ⊕ a [ y ] = c a[x]\oplus a[y]=c a[x]a[y]=c(就是第二个操作)。所以我们就要合并 x x x y y y。设 x x x的祖先是 a x ax ax y y y的祖先是 a y ay ay

  1. 首先要判矛盾。如果 a x = = a y ax==ay ax==ay,说明 x x x y y y在同一个集合。此时他们的祖先是共有的,那么 X o r [ x ] ⊕ X o r [ y ] Xor[x]\oplus Xor[y] Xor[x]Xor[y]就是 a [ x ] ⊕ a [ y ] a[x]\oplus a[y] a[x]a[y](祖先被异或了两次,抵消了)
    如果这个值不等于 c c c,那就爆了,就是有矛盾的。我们给合并函数设置一个 b o o l bool bool类型的返回值,如果出现了矛盾,返回 f a l s e false false,否则返回 t r u e true true
  2. 那么如果没有出现矛盾,我们就只能乖♂乖♂合并了。我们假设要把 a x ax ax接到 a y ay ay上。原本的 X o r [ a x ] Xor[ax] Xor[ax]肯定是 0 0 0(想一想 X o r [ ] Xor[] Xor[]的定义。然后 a x ax ax又是一个祖先,祖先和祖先的祖先(也就是自己)异或一下,那就是 0 0 0了)。但是现在我们要把它设置成 a [ a x ] ⊕ a [ a y ] a[ax]\oplus a[ay] a[ax]a[ay]。怎么办呢。。。
    我们发现 a [ x ] ⊕ a [ y ] a[x]\oplus a[y] a[x]a[y]是知道的,它等于 c c c(这是题目的要求)。我们现在以数学竞赛生的角度去破解这个玩意。因为有 a [ a x ] a[ax] a[ax]这个项,我们的已知数里面,哪个包含这个项呢?那当然是 X o r [ x ] Xor[x] Xor[x],它等于 a [ x ] ⊕ a [ a x ] a[x]\oplus a[ax] a[x]a[ax]。同理,我们还需要一个 X o r [ y ] Xor[y] Xor[y]项,尝试把这两个东西异或起来,等于:
    ( a [ a x ] ⊕ a [ a y ] ) ⊕ ( a [ x ] ⊕ a [ y ] ) (a[ax]\oplus a[ay]) \oplus (a[x]\oplus a[y]) (a[ax]a[ay])(a[x]a[y])
    = ( a [ a x ] ⊕ a [ a y ] ) ⊕ c =(a[ax]\oplus a[ay]) \oplus c =(a[ax]a[ay])c
    我们发现,此时再把 c c c给异或掉,我们就求出来我们要的那个东西了!!!
    此时,写出代码:
bool Merge(int x,int y,int c)
{
    int ax=Find(x),ay=Find(y);
    if(ax==ay)
    {
        if((Xor[x]^Xor[y])!=c) return false;
        return true;
    }
    if(ax==n) swap(ax,ay);
    Father[ax]=ay;
    Xor[ax]=Xor[x]^Xor[y]^c;//核心
    return true;
}//同样,也就是思维难度大,代码不长

part3. 如何用这个并查集

  1. 对于 1 1 1操作:我们设置一个临时节点 n n n(注意原来的 a a a数组中是 0 0 0 n − 1 n-1 n1,是没有 n n n这个点的)。令 a [ n ] = 0 a[n]=0 a[n]=0。那么这个赋值操作a[p]=v就相当于a[p]^a[n]=0,转化为 2 2 2操作
  2. 板子。直接套并查集
  3. 我们把询问中的这些数分成若干块,同一块里边祖先相等(即在同一个联通块内)。计算是否把祖先异或了偶数次。因为异或两次是珂以抵消的,所以异或偶数次也就珂以抵消了。然后把该块内的 X o r Xor Xor值异或起来,因为祖先被抵消了,所以就变成了 a a a的异或和。(如果祖先是 n n n,由于 a [ n ] = 0 a[n]=0 a[n]=0,不管异或了奇数次还是偶数次,都不会有变化。此时也是珂以求出联通块的答案的。)然后把每一块的答案都异或起来,就是这 k k k个数的异或和。但是注意,只要有一个块是未知的,那么我们就求不出答案。

好了这个题就做完了。代码:

#include<bits/stdc++.h>
using namespace std;
namespace Flandle_Scarlet
{
    #define N 20005
    int n,q,k;
    int nums[N],vis[N];
    char tmp[110];
    class DSU
    {
        public:
            int Father[N];
            int Xor[N];
            int Find(int x)
            {
                if (x!=Father[x])
                {
                    int fa=Father[x];
                    Father[x]=Find(Father[x]);
                    Xor[x]^=Xor[fa];
                }
                return Father[x];
            }

            bool Merge(int x,int y,int c)
            {
                int ax=Find(x),ay=Find(y);
                if(ax==ay)
                {
                    if((Xor[x]^Xor[y])!=c) return false;
                    return true;
                }
                if(ax==n) swap(ax,ay);
                Father[ax]=ay;
                Xor[ax]=Xor[x]^Xor[y]^c;
                return true;
            }
    }D;//毒瘤并查集
    int Query()
    {
        int smcnt,ans=0;
        memset(vis,0,sizeof(vis));
        for(int i=0;i<k;++i)
        {
            if (vis[i]) continue;
            smcnt=0;
            int ai=D.Find(nums[i]);//祖先
            for(int j=i;j<k;++j)
            {
                if (!vis[j] and ai==D.Find(nums[j])/*有相同的祖先*/)//查找同一个块内的数
                {
                    vis[j]=1;
                    ++smcnt;//同一个块内有多少元素(是same count的简写)
                    ans^=D.Xor[nums[j]];//每个Xor值异或起来
                }
            }
            if ((smcnt&1) and ai!=n)//此时是无解的情况
            {
                return -1;
            }
        }
        return ans;
    }
    void Soviet(int Case)
    {
        int u,v,w;
        printf("Case %d:\n",Case);
        int fact=0;
        bool flag=0;//记录有没有矛盾
        while(q--)
        {
            scanf("%s",tmp);
            if (tmp[0]=='I')
            {
                getchar();//会有一个多余的空格(在I和剩下的操作之间)
                gets(tmp);
                ++fact;
                int spacecnt=0;
                for(int i=0;tmp[i]!='\0';++i)
                {
                    if (tmp[i]==' ')
                    {
                        ++spacecnt;
                    }
                }
                if (spacecnt==1)
                {
                    sscanf(tmp,"%d%d",&u,&w);
                    v=n;
                }
                else
                {
                    sscanf(tmp,"%d%d%d",&u,&v,&w);
                }

                if (flag) continue;
                if (!D.Merge(u,v,w))
                {
                    printf("The first %d facts are conflicting.\n",fact);
                    flag=1;
                }
            }
            else
            {
                scanf("%d",&k);
                for(int i=0;i<k;++i)
                {
                    scanf("%d",&nums[i]);
                }

                if (flag) continue;
                int ans=Query();
                if (ans==-1)
                {
                    printf("I don't know.\n");
                }
                else
                {
                    printf("%d\n",ans);
                }
            }
        }
        putchar('\n');
    }

    void IsMyWife()
    {
        int Case=1;
        while(~scanf("%d%d",&n,&q),n+q)
        {
            for(int i=0;i<=n;i++)
            {
                D.Father[i]=i;
                D.Xor[i]=0;
            }
            Soviet(Case++);
        }
    }
}

int main()
{
    Flandle_Scarlet::IsMyWife();
    return 0;
}

回到总题解界面

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值