【uva12232/hdu3461】带权并查集维护异或值

时间:2023-03-10 05:33:47
【uva12232/hdu3461】带权并查集维护异或值

题意:

对于n个数a[0]~a[n-1],但你不知道它们的值,通过逐步提供给你的信息,你的任务是根据这些信息回答问题:
I P V :告诉你a[P] = V
I P Q V:告诉你a[P] XOR a[Q] = V
Q K P1..PK:询问a[P1]^a[P2]^...a[PK]的值
n<=20000

题解:
首先看前两个操作:
第一个操作我们可以新加一个节点a[n]=0,则a[p]=val转化为a[p]^a[n]=val。
那么所有知道值的点必定都与a[n]在同一个并查集之内。
我们可以用带权并查集,对每个点x维护fa[x],d[x]是a[x]^a[fa[x]]。
对与第三个操作,我们可以发现p1,p2,...,pk必定在某几个并查集之中。
如果某个并查集中有奇数个p,那就相当于a[p1]^a[p2]^..a[px] ^ 奇数次a[根](异或一个数偶数次相当于没有异或它)。
如果根不是a[n],也就是根的值未知,那么必定是不确定答案的。
否则我们就可以直接算出。

注意合并的时候如果有一个是a[n]所在集合,要让a[n]为根。

 #include<cstdio>
#include<cstdlib>
#include<cstring>
#include<iostream>
#include<ctime>
using namespace std; const int N=;
int n,m,fa[N],d[N],cnt[N],q[N];
char s[]; int findfa(int x)
{
if(fa[x]!=x)
{
int xx=fa[x];
fa[x]=findfa(fa[x]);
d[x]=d[x]^d[xx];
}
return fa[x];
} int main()
{
freopen("a.in","r",stdin);
freopen("a.out","w",stdout);
char ch;
int x,y,k,p,xx,yy,val,ans,bk,ok,num,T=;
while()
{
scanf("%d%d",&n,&m);
if(!n && !m) break;
printf("Case %d:\n",++T);
memset(cnt,,sizeof(cnt));
for(int i=;i<=n;i++) fa[i]=i,d[i]=;
num=;bk=;
for(int i=;i<=m;i++)
{
scanf("%s",s);
if(s[]=='I')
{
num++;
scanf("%d%d%c",&x,&y,&ch);
if(ch==' ') scanf("%d",&val);
else val=y,y=n;
if(!bk) continue;
xx=findfa(x);yy=findfa(y);
if(xx!=yy)
{
if(xx==n) swap(x,y),swap(xx,yy);//debug
fa[xx]=y;
d[xx]=val^d[x];
}
else if((d[x]^d[y])!=val) {printf("The first %d facts are conflicting.\n",num);bk=;}
}
else
{
scanf("%d",&k);
for(int j=;j<=k;j++)
{
scanf("%d",&q[j]);
findfa(q[j]);
cnt[fa[q[j]]]++;
}
if(!bk) continue;
ans=;ok=;
for(int j=;j<=k;j++)
{
x=q[j];
if(fa[x]!=n && (cnt[fa[x]]&)) {printf("I don't know.\n");ok=;break;}
ans^=d[x];
}
for(int j=;j<=k;j++) cnt[fa[q[j]]]--;
if(ok) printf("%d\n",ans);
}
// printf("i = %d\n",i);
// for(int j=0;j<n;j++) printf("%d fa = %d d = %d\n",j,fa[j],d[j]);
// printf("\n");
}
printf("\n");
}
return ;
}