hdu3234 Exclusive-OR(带权并查集)

题目

题目叙述来自思路来源,懒得敲了2333……

思路来源

https://blog.youkuaiyun.com/XY20130630/article/details/50638922

题解

考虑,把Xp XOR Xq=v这样的关系建成边,不妨p为root,则q与其相连,且置q的置为v,代表一种相对关系

后续,带权并查集维护的,也都是到祖先的这条链的异或值

Xp=v的一类型操作,将其视为Xp XOR 0=v,

由于原先的点的下标为0到n-1,则建一个虚点n,作一类型的0点,

 

考虑合并过程,如果相同祖先,但是到根的距离不满足差z的关系,则不合法

否则合法,按带权并查集的思想,若将px合并到py上去,

则将px的距离用py->y y->x x->px的关系,重新定义px的值

 

然后考虑询问,对每一堆相同rt的单独考虑,不同root的互不影响

不妨设x1,x2,...,xm的根相同,则由于每个值v[xi]是与根root的异或值,

最后的答案是,v_{x1}\bigoplus v_{x2}\bigoplus ... \bigoplus v_{xm}=x_{1}\bigoplus root\bigoplus x_{2}\bigoplus root\bigoplus ...\bigoplus x_{m}\bigoplus root

如果root的个数为偶,显然是可以消掉的;

为奇的话,由于root的值可以改变式子的值,故此时答案不唯一

具体实现的时候,特判一下root是不是虚点n

代码

#include<bits/stdc++.h>
using namespace std;
const int N=2e4+10,M=300,K=20;
int n,q,k,x,y,z,par[N],v[N],ask[K];
char s[N];
bool vis[K];
void init(int n){
	for(int i=0;i<=n;++i){
		par[i]=i;
		v[i]=0;
	}
}
int find(int x){
	if(par[x]==x)return x;
	int fa=par[x];//直接基类
	par[x]=find(par[x]);//路径压缩
	v[x]^=v[fa];//树上前缀和 两链变一链 直接连树根
	return par[x];//返回树根
}
//y^x=z
bool unite(int x,int y,int z){
	int px=find(x),py=find(y);
	if(px==py){
		if((v[x]^v[y])!=z)return 0;
		return 1;
	}
    if(px==n){//n为不动点 只能为根
        swap(px,py);
    }
	par[px]=py;
	v[px]=v[y]^z^v[x];
	return 1;
}
int query(){
    int fa,ans=0,num;
    for(int i=1;i<=k;++i){
        if(vis[i])continue;
        vis[i]=1;
        fa=find(ask[i]);
        ans^=v[ask[i]];
        num=1;
        for(int j=i+1;j<=k;++j){
            if(vis[j])continue;
            if(find(ask[j])==fa){
                vis[j]=1;
                ans^=v[ask[j]];
                num++;
            }
        }
        if((num&1) && fa!=n){
            return -1;
        }
    }
    return ans;
}
int main(){
    int c=0;
    while(~scanf("%d%d",&n,&q)){
        if(!n && !q)break;
        bool non=false;
        int fac=0;
        init(n);
        printf("Case %d:\n",++c);
        while(q--){
            scanf("%s",s);
            if(s[0]=='I'){
                fac++;
                getchar();
                fgets(s,250,stdin);
                int sp=0;//space
                for(int i=0;s[i];++i){
                    sp+=(s[i]==' ');
                }
                if(sp==1){
                    sscanf(s,"%d%d",&x,&z);
                    y=n;
                }
                else{
                    sscanf(s,"%d%d%d",&x,&y,&z);
                }
                if(non)continue;
                if(!unite(x,y,z)){
                    printf("The first %d facts are conflicting.\n",fac);
                    non=true;
                }
            }
            else{
                scanf("%d",&k);
                for(int i=1;i<=k;++i){
                    vis[i]=0;
                    scanf("%d",&ask[i]);
                }
                if(non)continue;
                int ans=query();
                if(ans==-1)puts("I don't know.");
                else printf("%d\n",ans);
            }
        }
        puts("");
    }
    return 0;
}

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值