题目大意:
给出一个
n
n
n 个数的序列
a
a
a,每次操作可以将一个数变成整个序列的值的异或
求最少需要多少次才能将
a
a
a 变成目标序列
b
b
b
无法完成输出
−
1
-1
−1
1
<
=
N
<
=
100000
1<=N<=100000
1<=N<=100000
数
的
大
小
<
2
30
数的大小<2^{30}
数的大小<230
分析:
考虑操作的本质
只要按位稍微分析一下,就可以发现,题目相当于一开始手里抓着整个序列 a 的异或,
一次操作可以将手上的数与序列中的某个数换过来
如果能够完成,方便起见将 a 的异或和 b 的异或分别加到序列末,排序后两个序列显然完全相同,
不相同就无解,
然后我么发现只有 a[i]!=b[i]的位置我们是需要调整的
那么将离散数值,并将a[i]与 b[i]连边
考虑统计答案
将一个联通块内换完所需要的次数明显就是联通块大小。
而联通块之间跳需要 1 的代价
此时由于我们一开始手上抓着的是 a 的异或和
如果它刚好在某一个联通块里面就不用考虑了,否则就必须将它自己看做一个大小为 0 的联通块
联通块可以用并查集维护
代码:
#include <algorithm>
#include <iostream>
#include <cstring>
#include <cstdio>
#include <cmath>
#include <queue>
#include <map>
#define N 100005
using namespace std;
int a[N], b[N], c[N], d[N], fa[N*2];
int n, m, num, ans;
map <int, int> rp;
int Find(int x)
{
if (fa[x] == x) return x;
return (fa[x] = Find(fa[x]));
}
int main()
{
freopen("duliu.in", "r", stdin);
freopen("duliu.out", "w", stdout);
scanf("%d", &n); int tot1 = 0, tot2 = 0;
for (int i = 1; i <= n; i++) scanf("%d", &a[i]), tot1 ^= a[i]; a[n + 1] = tot1;
for (int i = 1; i <= n; i++) scanf("%d", &b[i]), tot2 ^= b[i]; b[++n] = tot2;
for (int i = 1; i <= n; i++) c[i] = a[i], d[i] = b[i];
sort(c + 1, c + n + 1); sort(d + 1, d + n + 1);
bool flag = 1; for (int i = 1; i <= n; i++) if (c[i] != d[i]) flag = 0;
if (!flag) { printf("-1\n"); return 0; }
for (int i = 1; i < n; i++) if (a[i] != b[i]) ++ans;
if (!ans) { printf("0\n"); return 0; }
for (int i = 1; i <= n; i++)
if (a[i] != b[i] || i == n)
{
if (!rp[a[i]]) rp[a[i]] = ++num;
if (!rp[b[i]]) rp[b[i]] = ++num;
}
for (int i = 1; i <= num; i++) fa[i] = i;
for (int i = 1; i <= n; i++)
if (a[i] != b[i]) fa[Find(rp[a[i]])] = Find(rp[b[i]]);
for (int i = 1; i <= num; i++) if (fa[i] == i) ++ans;
printf("%d\n", ans - 1);
}