ARC145E 解题报告()

ARC145E 解题报告

Description

给定两个长度为\(n\)的数列\(A,B\),可以执行如下操作:

  • 选择一个\(K\),对于每个满足\(2\le i\le K\)的正整数\(i\),同时将\(A_i\)变为\(A_i\oplus A_{i-1}\),其中\(\oplus\)代表按位异或。

问能否在\(70000\)次操作内将\(A\)变为\(B\),并输出一组可行操作。

\(n\le 1000, 0\le A_i,B_i<2^{60}\)

Sol

Step1:逆向思维

假设一次操作后\([2,K]\)的\(A_i\)变为\(A’_i\),那么我们有\(A’_i=A_i\oplus A_{i-1}\)

那么我们用\(A’\)数组来表示原来的\(A\)数组,就变为\(A_i=A’_1\oplus A’_2 \oplus…\oplus A’_i\)

那么问题转化为如下:

  • 选择一个\(K\),对于每个满足\(2\le i\le K\)的正整数\(i\),同时将\(B_i\)变为\(B_1\oplus B_2\oplus…\oplus B_i\)。

问能否在\(70000\)次操作内将\(B\)变为\(A\)

最终将所有操作倒序输出即可。

Step2:判断可行

可以用数学归纳法证明,每个\(B_i\)都可以在若干次操作后变为\(B_i\oplus \{B_1,…,B_{i-1}\}\)的一个子集的异或和。

只需要判断\(A_i\oplus B_i\)是否可以由前面若干个数异或得出。

使用线性基即可。

Step3:输出操作

由于一个\(K\)不会影响到\(K\)之后的部分,于是我们从后往前进行操作。

对于位置\(i\),因为过程中肯定不会执行\(K>i\)的操作影响后面已经定好的数,最后肯定会执行一次\(K=i\)使得\(B_i=A_i\),所以我们考虑在若干次操作后让\(B_1\oplus B_2\oplus…\oplus B_i\oplus A_i=0\),再执行一次\(i\)操作。

由于每个数都可以用在线性基里的数的异或和来表示出(因为在插入线性基的过程中这个数要么变成\(0\)要么成功插入线性基),我们表示出当前的\(B_1\oplus B_2\oplus…\oplus B_i\oplus A_i\),记为\(m_1c_1\oplus m_2c_2\oplus…(m=0\)或\(1\),\(c\)为线性基里的数\()\)。

我们要通过若干次操作让所有的\(m_1,m_2,…=0\),考虑对于一个数\(j\)满足\(B_j\)在线性基内,那么执行操作\(K=j+1\)会使\(B_j\)的出现次数的奇偶性发生改变(执行操作后会使\(B_{j+1}\)异或上一个\(B_j\),而由于我们按照\(1,2,…,n\)的顺序将数插入线性基,那么前面的数必然不可能包含\(B_j\),所以只会在\(B_{j+1}\)处多上一个\(B_j\)),所以当我们枚举到一个线性基里的数\(c_k\)且\(m_k=1\),那么我们只需要执行操作\(K=pos_{c_k}+1\)即可让\(m_k=0\)且任意\(i>k\)的\(m_i\)都不受影响。

从后往前做一次即可让所有的\(m=0\),也就是\(B_1\oplus B_2\oplus…\oplus B_i\oplus A_i=0\),再执行一次操作\(K=i\)即可。

线性基里的数最多\(\log\max \{B\}=60\)个,我们只会对线性基里的数执行操作,所以操作次数最多为\(61000<70000\)次。

但是每次重新跑线性基是\(O(n)\)的复杂度,总时间复杂度为\(O(n^2\times 60^2)\),过不去,我们考虑优化跑线性基的过程。

我们可以发现一个性质:执行一次操作后,原来在线性基里的数还是在线性基里,不在的还是不在,可以用数学归纳法证明:

执行一次操作后,\(B_1\)不会改变,所以其肯定在线性基中(如果\(B_1\not=0\))

假设对于\(k\ge 2\),\(B_1,…,B_{k-1}\)是否在线性基中都不发生改变,那么我们分下面两种情况讨论:

  • \(B_k\)在线性基中:由于之前的\(B_1,…,B_{k-1}\)不能选出一些数使其异或和等于\(B_k\),执行操作后不会产生新的数(\(B_i\)都是由原来的\(B_1,…,B_i\)异或得来的),所以仍然不能选出一些数使其异或和等于\(B_k\),\(B_k\)应当插入线性基。
  • \(B_k\)不在线性基中:由于执行操作后原有的数也不会消失,所以\(B_k\)仍然可以通过前面一些数异或得出,不能被插入线性基。

得证。

所以每次只需插入原来在线性基里的数即可,时间复杂度\(O(n\times 60^3)\)

Code

#include<bits/stdc++.h>
#define int long long
using namespace std;
int Read() {
	int x = 0, f = 1;
	char ch = getchar();
	while(!isdigit(ch) && ch != '-')  ch = getchar();
	if(ch == '-')  f = -1, ch = getchar();
	while(isdigit(ch))  x = (x << 3) + (x << 1) + ch - '0', ch = getchar();
	return (f == -1) ? -x : x;
}
int val[64], p[64][64], num, stk[64], tp;
int Ins(int x) {
	int f[64], cnt = 0;
	for(int i = 60; i >= 0; i--) {
		if(!((x >> i) & 1ll))  continue ;
		if(!val[i]) {
			val[i] = x;
			p[i][0] = ++num;
			for(int k = 1; k <= cnt; k++)
				for(int j = 0; j <= 60; j++)
					p[i][j] ^= p[f[k]][j];
			p[i][num] ^= 1;  return 1;
		}
		f[++cnt] = p[i][0]; x ^= val[i];
	}
	return 0;
}
int n, a[1005], b[1005], ans[80005], CNT = 0;
int getval(int x, int pos) {
	int res = 0;
	for(int i = 60; i >= 0; i--) {
		if(!((x >> i) & 1ll))  continue ;
		x ^= val[i], res ^= p[i][pos];
	}
	return res;
}
void doit(int pos) {
	int sum = 0;
	for(int i = 1; i <= pos; i++)  b[i] = sum = sum ^ b[i];
}
signed main() {
	n = Read();
	for(int i = 1; i <= n; i++)  a[i] = Read();
	for(int i = 1; i <= n; i++)  b[i] = Read();
	for(int i = 1; i <= n; i++) {
		if(Ins(a[i] ^ b[i])) {
			puts("No");
			return 0;
		}
		if(Ins(b[i]))  stk[++tp] = i;
	}
	puts("Yes");
	for(int i = n; i >= 1; i--) {
		if(a[i] == b[i])  continue ;
		for(int j = tp; j >= 1; j--) {
			if(stk[j] >= i)  continue ;
			memset(val, 0, sizeof(val));
			memset(p, 0, sizeof(p)); num = 0;
			for(int k = 1; k <= j; k++)  Ins(b[stk[k]]);
			int sum = a[i];
			for(int k = 1; k <= i; k++)  sum ^= b[k];
			if(getval(sum, j))  doit(stk[j] + 1), ans[++CNT] = stk[j] + 1;
		}
		doit(i), ans[++CNT] = i;
		assert(a[i] == b[i]);
	}
	cout << CNT << endl;
	for(int i = CNT; i >= 1; i--)  printf("%lld ", ans[i]);
    return 0;
}
————————

ARC145E 解题报告

Description

给定两个长度为\(n\)的数列\(A,B\),可以执行如下操作:

  • 选择一个\(K\),对于每个满足\(2\le i\le K\)的正整数\(i\),同时将\(A_i\)变为\(A_i\oplus A_{i-1}\),其中\(\oplus\)代表按位异或。

问能否在\(70000\)次操作内将\(A\)变为\(B\),并输出一组可行操作。

\(n\le 1000, 0\le A_i,B_i<2^{60}\)

Sol

Step1:逆向思维

假设一次操作后\([2,K]\)的\(A_i\)变为\(A’_i\),那么我们有\(A’_i=A_i\oplus A_{i-1}\)

那么我们用\(A’\)数组来表示原来的\(A\)数组,就变为\(A_i=A’_1\oplus A’_2 \oplus…\oplus A’_i\)

那么问题转化为如下:

  • 选择一个\(K\),对于每个满足\(2\le i\le K\)的正整数\(i\),同时将\(B_i\)变为\(B_1\oplus B_2\oplus…\oplus B_i\)。

问能否在\(70000\)次操作内将\(B\)变为\(A\)

最终将所有操作倒序输出即可。

Step2:判断可行

可以用数学归纳法证明,每个\(B_i\)都可以在若干次操作后变为\(B_i\oplus \{B_1,…,B_{i-1}\}\)的一个子集的异或和。

只需要判断\(A_i\oplus B_i\)是否可以由前面若干个数异或得出。

使用线性基即可。

Step3:输出操作

由于一个\(K\)不会影响到\(K\)之后的部分,于是我们从后往前进行操作。

对于位置\(i\),因为过程中肯定不会执行\(K>i\)的操作影响后面已经定好的数,最后肯定会执行一次\(K=i\)使得\(B_i=A_i\),所以我们考虑在若干次操作后让\(B_1\oplus B_2\oplus…\oplus B_i\oplus A_i=0\),再执行一次\(i\)操作。

由于每个数都可以用在线性基里的数的异或和来表示出(因为在插入线性基的过程中这个数要么变成\(0\)要么成功插入线性基),我们表示出当前的\(B_1\oplus B_2\oplus…\oplus B_i\oplus A_i\),记为\(m_1c_1\oplus m_2c_2\oplus…(m=0\)或\(1\),\(c\)为线性基里的数\()\)。

我们要通过若干次操作让所有的\(m_1,m_2,…=0\),考虑对于一个数\(j\)满足\(B_j\)在线性基内,那么执行操作\(K=j+1\)会使\(B_j\)的出现次数的奇偶性发生改变(执行操作后会使\(B_{j+1}\)异或上一个\(B_j\),而由于我们按照\(1,2,…,n\)的顺序将数插入线性基,那么前面的数必然不可能包含\(B_j\),所以只会在\(B_{j+1}\)处多上一个\(B_j\)),所以当我们枚举到一个线性基里的数\(c_k\)且\(m_k=1\),那么我们只需要执行操作\(K=pos_{c_k}+1\)即可让\(m_k=0\)且任意\(i>k\)的\(m_i\)都不受影响。

从后往前做一次即可让所有的\(m=0\),也就是\(B_1\oplus B_2\oplus…\oplus B_i\oplus A_i=0\),再执行一次操作\(K=i\)即可。

线性基里的数最多\(\log\max \{B\}=60\)个,我们只会对线性基里的数执行操作,所以操作次数最多为\(61000<70000\)次。

但是每次重新跑线性基是\(O(n)\)的复杂度,总时间复杂度为\(O(n^2\times 60^2)\),过不去,我们考虑优化跑线性基的过程。

我们可以发现一个性质:执行一次操作后,原来在线性基里的数还是在线性基里,不在的还是不在,可以用数学归纳法证明:

执行一次操作后,\(B_1\)不会改变,所以其肯定在线性基中(如果\(B_1\not=0\))

假设对于\(k\ge 2\),\(B_1,…,B_{k-1}\)是否在线性基中都不发生改变,那么我们分下面两种情况讨论:

  • \(B_k\)在线性基中:由于之前的\(B_1,…,B_{k-1}\)不能选出一些数使其异或和等于\(B_k\),执行操作后不会产生新的数(\(B_i\)都是由原来的\(B_1,…,B_i\)异或得来的),所以仍然不能选出一些数使其异或和等于\(B_k\),\(B_k\)应当插入线性基。
  • \(B_k\)不在线性基中:由于执行操作后原有的数也不会消失,所以\(B_k\)仍然可以通过前面一些数异或得出,不能被插入线性基。

得证。

所以每次只需插入原来在线性基里的数即可,时间复杂度\(O(n\times 60^3)\)

Code

#include<bits/stdc++.h>
#define int long long
using namespace std;
int Read() {
	int x = 0, f = 1;
	char ch = getchar();
	while(!isdigit(ch) && ch != '-')  ch = getchar();
	if(ch == '-')  f = -1, ch = getchar();
	while(isdigit(ch))  x = (x << 3) + (x << 1) + ch - '0', ch = getchar();
	return (f == -1) ? -x : x;
}
int val[64], p[64][64], num, stk[64], tp;
int Ins(int x) {
	int f[64], cnt = 0;
	for(int i = 60; i >= 0; i--) {
		if(!((x >> i) & 1ll))  continue ;
		if(!val[i]) {
			val[i] = x;
			p[i][0] = ++num;
			for(int k = 1; k <= cnt; k++)
				for(int j = 0; j <= 60; j++)
					p[i][j] ^= p[f[k]][j];
			p[i][num] ^= 1;  return 1;
		}
		f[++cnt] = p[i][0]; x ^= val[i];
	}
	return 0;
}
int n, a[1005], b[1005], ans[80005], CNT = 0;
int getval(int x, int pos) {
	int res = 0;
	for(int i = 60; i >= 0; i--) {
		if(!((x >> i) & 1ll))  continue ;
		x ^= val[i], res ^= p[i][pos];
	}
	return res;
}
void doit(int pos) {
	int sum = 0;
	for(int i = 1; i <= pos; i++)  b[i] = sum = sum ^ b[i];
}
signed main() {
	n = Read();
	for(int i = 1; i <= n; i++)  a[i] = Read();
	for(int i = 1; i <= n; i++)  b[i] = Read();
	for(int i = 1; i <= n; i++) {
		if(Ins(a[i] ^ b[i])) {
			puts("No");
			return 0;
		}
		if(Ins(b[i]))  stk[++tp] = i;
	}
	puts("Yes");
	for(int i = n; i >= 1; i--) {
		if(a[i] == b[i])  continue ;
		for(int j = tp; j >= 1; j--) {
			if(stk[j] >= i)  continue ;
			memset(val, 0, sizeof(val));
			memset(p, 0, sizeof(p)); num = 0;
			for(int k = 1; k <= j; k++)  Ins(b[stk[k]]);
			int sum = a[i];
			for(int k = 1; k <= i; k++)  sum ^= b[k];
			if(getval(sum, j))  doit(stk[j] + 1), ans[++CNT] = stk[j] + 1;
		}
		doit(i), ans[++CNT] = i;
		assert(a[i] == b[i]);
	}
	cout << CNT << endl;
	for(int i = CNT; i >= 1; i--)  printf("%lld ", ans[i]);
    return 0;
}