该死的期末复习终于结束了。。。
暑 假 来 了 \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。
- 首先要判矛盾。如果
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。 - 那么如果没有出现矛盾,我们就只能乖♂乖♂合并了。我们假设要把
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操作:我们设置一个临时节点
n
n
n(注意原来的
a
a
a数组中是
0
0
0到
n
−
1
n-1
n−1,是没有
n
n
n这个点的)。令
a
[
n
]
=
0
a[n]=0
a[n]=0。那么这个赋值操作
a[p]=v就相当于a[p]^a[n]=0,转化为 2 2 2操作 - 板子。直接套并查集
- 我们把询问中的这些数分成若干块,同一块里边祖先相等(即在同一个联通块内)。计算是否把祖先异或了偶数次。因为异或两次是珂以抵消的,所以异或偶数次也就珂以抵消了。然后把该块内的 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;
}
博客介绍了如何使用并查集解决HDU 3234 Exclusive-OR问题,包括题意解析、数据结构、路径压缩、合并操作以及如何利用并查集处理题目中的异或操作。通过路径压缩实现O(1)复杂度更新Xor[i],合并操作时判断和处理矛盾,最后展示了如何应用并查集解决实际问题。
&spm=1001.2101.3001.5002&articleId=94397581&d=1&t=3&u=0e33afe28c2b4b158dcc9813303720b7)
621

被折叠的 条评论
为什么被折叠?



