[AGC016D] XOR Replace 题解

[AGC016D] XOR Replace

来自 @qzmoot 同一机房的同学的题解。

模拟赛用不同的思路场切了。

题面大意:一个序列,一次操作可以将某个位置变成整个序列的异或和。 问最少几步到达目标序列。

来自梦熊的题面:

有一个长度为 n n n 的序列 a a a,有以下函数:

void update(int u){
	int w=0;
	for(int i=1;i<=n;i++) w^=a[i];
	a[u]=w;
}

你可以执行这个函数若干次,其中参数 u u u 由你指定,请问将序列 a a a 修改为序列 b b b 的最小调用次数是多少。

分析:

操作逻辑简化

我们先要知道这个操作在干什么。

如果你指定一个 u u u,使 w = a u w = a_u w=au a u = ⊕ i = 1 n a i a_u = \oplus_{i = 1}^n a_i au=i=1nai

那么当你再指定一个 u ′ u^{\prime} u 时, a u ′ = ⊕ i = 1 n a i = w a_{u^{\prime}} = \oplus_{i = 1}^n a_i = w au=i=1nai=w

这是由于异或具有自反性。

那么,这个操作就相当于你有一个初始为 a a a 的序列,同时手里还纂着一个 ⊕ i = 1 n a i \oplus_{i = 1}^n a_i i=1nai,你每次可将手中的数和序列中的数交换,直到序列 a a a 变成序列 b b b

合法情况判断

接下来判断的是怎样才是合法的局面。

根据上面的分析,容易发现这个序列 a a a 有两种情况:

  1. 序列 a a a 和序列 b b b 的数种类相同,且每种种类的数的个数相同。
  2. 序列 a a a 和序列 b b b 的数种类只有一种不同,该种类数的个数是 1 1 1,且该数为初始的 ⊕ i = 1 n a i \oplus_{i = 1}^n a_i i=1nai
最小操作数

接下来是最小操作数的分析。

我们的操作肯定是取出一个数,直接放进与 b b b 对应的位置。

因为只有合法局面(序列中的数一定是一一对应的)才会计算最小操作数,所以这样做一定是最优的。

并且 a a a b b b 已经匹配上的位置可以不用考虑。

那么,你会发现我们的拿取操作形成了一个环或链。

因此,我们可以将 a a a b b b 中的对应位置的数连边。

其必然形成若干简单环和链,如图:

img

img

对于链,其中必定有 ⊕ i = 1 n a i \oplus_{i = 1}^n a_i i=1nai,这时最小操作数为链的边数个数

对于环,其中必定没有 ⊕ i = 1 n a i \oplus_{i = 1}^n a_i i=1nai,这时我们会先将 ⊕ i = 1 n a i \oplus_{i = 1}^n a_i i=1nai 放到任意一个位置,最后将所有环中数字位置调整完后取出来,最小操作数为环的边数 + 1

用并查集统计答案就可以过了。

最后由于 a i , b i < 2 30 a_i,b_i < 2^{30} ai,bi<230,所以用并查集计算答案之前需要离散化。

这种方法好像不需要判孤立点(大概吧,本人没有仔细研究)。

AC-code:

#include<bits/stdc++.h>
using namespace std;
#define int long long
int rd() {
	int x = 0, w = 1;
	char ch = 0;
	while (ch < '0' || ch > '9') {
		if (ch == '-') w = -1;
		ch = getchar();
	}
	while (ch >= '0' && ch <= '9') {
		x = x * 10 + (ch - '0');
		ch = getchar();
	}
	return x * w;
}
void wt(int x) {
	static int sta[35];
	int f = 1;
	if(x < 0) f = -1,x *= f;
	int top = 0;
	do {
		sta[top++] = x % 10, x /= 10;
	} while (x);
	if(f == -1) putchar('-');
	while (top) putchar(sta[--top] + 48);
}
signed main() {
	int n = rd(),top = 0;
	vector<int> a(n),b(n),c(n * 2),s(n * 2),siz(n * 2),t(n * 2);
	vector<bool> bok(n * 2),ext(n * 2);
	vector<int> in(n * 2);
	for(int i = 0;i<n;i++) a[i] = rd();
	for(int i = 0;i<n;i++) b[i] = rd();
	for(int i = 0;i<n;i++) c[top++] = a[i],c[top++] = b[i];
	int w = 0;
	for(int i = 0;i<n;i++) w ^= a[i];
	c.emplace_back(w);	
	sort(c.begin(),c.end());
	top = unique(c.begin(),c.end()) - c.begin() - 1;
	for(int i = 0;i<n;i++) 
		a[i] = lower_bound(c.begin(),c.begin() + top + 1,a[i]) - c.begin(),
		b[i] = lower_bound(c.begin(),c.begin() + top + 1,b[i]) - c.begin();
	w = lower_bound(c.begin(),c.begin() + top + 1,w) - c.begin();
	for(int i = 0;i<n;i++) t[a[i]]++;
	t[w]++;
	for(int i = 0;i<n;i++) {
		t[b[i]]--;
		if(t[b[i]] < 0) {puts("-1");return 0;}
	}
	bool flag = false;
	for(int i = 0;i<n;i++) 
		if(a[i] != b[i]) {flag = true;break;}
	if(!flag) {wt(0);return 0;}
	for(int i = 0;i<n * 2;i++) {s[i] = i;ext[i] = 0;siz[i] = 0;bok[i] = 0;}
	for(int i = 0;i<n;i++) {
		if(a[i] == b[i]) continue;
		bok[a[i]] = bok[b[i]] = true;
		siz[b[i]]++;
		if(w == b[i]) ext[b[i]] = true; 
	}
	auto find = [&](auto self,int x) -> int {
		if(s[x] ^ x) 
			s[x] = self(self,s[x]);
		return s[x];
	};
	for(int i = 0;i<n;i++) {
		if(a[i] == b[i]) continue;
		int fa = find(find,a[i]),fb = find(find,b[i]);
		if(fa != fb) {
			s[fa] = fb;
			siz[fb] += siz[fa];
			ext[fb] = ext[fb] | ext[fa];
		}
	}
	int ans = 0;
	for(int i = 0;i<n * 2;i++) {
		if(bok[i] && find(find,i) == i) {
			if(ext[find(find,i)]) ans += siz[find(find,i)];
			else ans += siz[find(find,i)] + 1;
		}
	}
	wt(ans);
	return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值