异或(Exclusive-OR,Wuhan 2009,LA 4487)

20 篇文章 0 订阅
9 篇文章 0 订阅

AC通道:http://acm.hdu.edu.cn/showproblem.php?pid=3234
(注意,此网站的样例有点问题,每两个Case之间还需要增加一个空行)


异或


Description

有n个小于 220 的非负整数 X0X1Xn1 ,但你并不知道它们的值。
我会逐步提供一些信息,你的任务是根据这些信息回答问题。

如表:

指令说明
I p v 我告诉你Xp=v
I p q v我告诉你 Xp XOR Xq = v
Q k p1 p2 p3 pk 你需要回答 Xp1 XOR Xp2 XOR XOR Xpk 的值

Input

输入最多包含10组数据。
每组数据第一行为两个整数 n Q 1n200002Q40000
以下 Q 行按顺序给出每条信息或者问题。
其中参数k为不超过15的正整数, v 是小于220的非负整数。
信息和问题混在一起,在回答每个问题时只能依据那之前给出的信息。
输入结束标志为 n=Q=0


Output

对于每组数据,输出测试数据编号,然后是各个问题的答案。
如果某个问题的答案不能唯一确定,输出“I don’t know.”;
如果某条信息和之前的信息矛盾,输出“The first i facts are conflicting.”,然后忽略之后所有的问题。
每组数据之后输出一个换行。


Sample Input

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


Sample Output

Case 1:
I don’t know.
3
1
2

Case 2:
4

Case 3:
7
The first 2 facts are conflicting.


Solution

本题可以利用加权并查集解决。

异或具有很多奇妙的性质:
- a ^ b = b ^ a
- ( a ^ b) ^ c = a ^ ( b ^ c);
- 若 a ^ b = c ,那么a ^ c = b

Xp XOR Xq = v ,那么我们可以把Xp Xq 连一条边,权值为 v
Xp = v ,那么就有Xp XOR 0 = v,所以我们可以把 Xp n+1 连一条边,权值为 v

所以我们就成功处理完了信息。
那么怎样回答呢?

对于有相同根节点的节点a1,a2,a3,am,设它们与根结点的距离为 wi ,则
a1 ^ a2 ^ a3 ^ … ^ am
= root ^ w1 ^ root ^ w2 ^ … ^ root ^ wm

综上所述,
- 若 root 的个数为奇数,及m为奇数,则这些节点的异或值为 root ^ w1 ^ w2 ^ … ^ wm
- 若 root 的个数为偶数,及m为偶数,则这些节点的异或值为 w1 ^ w2 ^ … ^ wm

m 为奇数且根结点的序号不为n+1,那么就标志着答案不能唯一确定;
否则就继续遍历下一个集合。


Code

#include <iostream>
#include <cstdio>
#include <cstring>

using namespace std;

int n,q,TTT=0;
int vs[200100];
int wan[200];
char ques[100000];
bool wan2[200];
int fa[200100];

int find(int x){
    int tmp;
    if(fa[x]!=x)tmp=find(fa[x]);
    else tmp=x;
    vs[x]^=vs[fa[x]];
    fa[x]=tmp;
    return tmp; 
}

bool merge(int x,int y,int z){
    int fx=find(x),fy=find(y);
    if(fx==fy){
        if((vs[x]^vs[y])!=z)return false;
        return true;
    }
    if(fx==n+1){
        fa[fy]=fx;
        vs[fy]=vs[x]^vs[y]^z; 
        return true;
    }
    fa[fx]=fy;
    vs[fx]=vs[y]^vs[x]^z;
    return true;
}

int read(int&x,int where){
    x=0;
    for(int i=where;;i++){
        if(ques[i]>='0'&&ques[i]<='9')x=x*10+ques[i]-'0';
        else return i-1;
    }
}

int main(){
    while(scanf("%d%d",&n,&q)!=EOF&&n+q){
        TTT++;
        printf("Case %d:\n",TTT);
        bool flag=false;
        getchar();
        int TT=0;
        for(int i=0;i<=n+1;i++)fa[i]=i,vs[i]=0;
        for(int i=1;i<=q;i++){
            fgets(ques,1000,stdin);
            if(ques[0]=='I'){
                TT++;
                int t=2,x,y,z,l=strlen(ques);
                int r=read(x,t);
                int v=read(y,r+2);
                if(flag)continue;
                if(v==l-1||(v==l-2&&(ques[l-1]=='\n'||ques[l-1]=='\r'))){
                    if(!merge(x,n+1,y)){
                        printf("The first %d facts are conflicting.\n",TT); 
                        flag=true;
                    }
                }
                else{
                    read(z,v+2);
                    if(!merge(x,y,z)){
                        printf("The first %d facts are conflicting.\n",TT); 
                        flag=true;
                    }
                }
            }
            else{
                int t=2,k,ans=0;
                int l=read(k,t);
                for(int j=1;j<=k;j++)
                    l=read(wan[j],l+2);
                if(flag)continue;
                memset(wan2,0,sizeof wan2);
                for(int j=1;j<=k;j++){
                    if(wan2[j])continue;
                    wan2[j]=true;
                    int fj=find(wan[j]),cnt=1;
                    ans^=vs[wan[j]];
                    for(int lss=j+1;lss<=k;lss++){
                        if(!wan2[lss]&&find(wan[lss])==fj){
                            wan2[lss]=true;
                            cnt++;
                            ans^=vs[wan[lss]]; 
                        }
                    }
                    if(fj!=n+1&&(cnt&1)){
                        printf("I don't know.\n");
                        goto nxt;
                    }
                }
                printf("%d\n",ans);
            }
nxt:;
        }
        printf("\n");
    }   
    return 0;
}
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值