MX 暑假集训 7.15

はるのはるか(Faraway Spring) feat.初音ミク / sasakure.UK

已被神秘构造击败。

T1: 二选一

题意

有一个隐藏的在 \(0\)\(2^B-1\) 的整数 \(Z\),你需要给出 \(8\) 个数,满足每个数都在 \([0,2^B-1]\) 之间,对于每个数 \(X\),交互库会返回一个数 \(Y\),满足其要么等于 \(Z\)\(X\) 按位与的值,要么等于 \(Z\)\(X\) 按位或的值,你需要同时提交这 \(8\) 个数并根据返回的 \(8\) 个值给出 \(Z_1\)\(Z_2\),满足要么 \(Z=Z_1\),要么 \(Z=Z_2\),你不需要返回 \(Z_1\)\(Z_2\),只需要给出这 \(8\) 个数,满足无论 \(Z\) 是哪个数,无论交互库如何返回,都能找到合法的 \(Z_1\)\(Z_2\) 即可。

\(1\le B\le 64\)

solution

神仙题。膜拜 Hukaidi8566 大人。

第一眼:这不是我们的 CF2246E 吗。

首先想到的是二进制分组,对于第 \(i\) 次询问,给出的数在第 \(j\) 位上为 \(1\),当且仅当 \(j\) 的第 \(i\) 位上为 \(1\),输出这个数和这个数取反后的数,即可解决 \(B\le 16\) 的部分。原因是若返回的数和给出的数不同,则能够确定这次的运算是什么,同时确定一半的位置是什么,那么就可以递归成一个完全相同的子问题。发现若两个数返回的值都与给出的数相同,则 \(Z\) 不是 \(0\) 就是 \(2^B-1\),反证法可以容易证明,就不提出详细证明了。

例如,\(B=16\) 时输出的数二进制下如下。

0000000011111111
1111111100000000
0000111100001111
1111000011110000
0011001100110011
1100110011001100
0101010101010101
1010101010101010

但是这样很劣,我们用了两个数才确定了一个位,我们考虑优化一下,每次只用一个数来确定,但这样会出现什么问题呢?若返回的值与我们给出的数一致,则我们不知道这一个位置进行了与操作还是或操作,从而无法确定一些位置的值到底是什么。

虽然一次询问如果返回了这个数本身我们无法确定任意一个位置,但是对于每个位置,要么全部大于等于给出这个数的这个位置,要么全部小于等于给出的这个数的这个位置,分别对应按位与操作和按位或操作,即:

  • 如果是按位与操作,若给出数这个位置是 \(0\),则实际这个位置可能是 \(0\)\(1\),给出 \(1\) 则实际只可能是 \(1\)
  • 如果是按位或操作,若给出数这个位置是 \(0\),则实际只可能是 \(0\),给出 \(1\) 则实际可能是 \(0\)\(1\)

我们发现一个关键性质:对于所有不能确定类型的操作,要么都是按位与,要么都是按位或,证明如下:

若存在给出两个数返回值相同,对应的位分别为 \(i,j\),那么若操作 \(i\) 是按位与,操作 \(j\) 是按位或,则找到一个第 \(i\) 位为 \(1\) 且第 \(j\) 位为 \(0\) 的位置,由于返回值与给出的数相同,则对于操作 \(i\),这个位置必须为 \(1\),对于操作 \(j\),这个位置必须为 \(0\),矛盾。

那么对于不能确定类型的所有操作,只有两种情况,一种是全是按位与,一种是全是按位或。

我们还剩下 \(8-\log_2 64=2\) 次剩余操作,要将可能的情况数缩减到两种。

考虑一个和上面 \(B=16\) 的表格类似的分治结构,相当于在每层填上一个“符号”,使得每个位置上的值,都符合每一层上面对应的数和符号。

以下是一个例子,问号表示不确定。

<=  0000000011111111
?   0000111100001111
<=  0011001100110011
?   0101010101010101
ans:0000000000??00??

每个位置,若上面有一层对应 \(\le 0\),那么这个位置为 \(0\),相应的,若有一层对应 \(\ge 1\),则这个位置为 \(1\)

我们要将操作上的问号都填成大于等于和小于等于中的一种,那么最多只有两个位置可能是 \(0\)\(1\) 中的一种,因为去掉确定的位置后,不确定的位置中最多有一个位置,满足它上面所有不确定操作的层上对应的数都为 \(0\),都为 \(1\) 也同理,由于符号只能填一样的,所以其他位置上一定存在层使得 \(\ge 0,\ge 1\)\(\le 0,\le 1\),都可以确定出来。

那么只剩下了最多四种情况,最复杂的情况是上面层全是 \(0\) 的位置和全是 \(1\) 的位置都没有确定,但是我们可以通过构造两个数,根据每个位置二进制下 \(1\) 个数的奇偶性来划分,然后进行大力分讨,从而确定最终的数。

将所有确定的位置去掉,对于剩下的位置,本质上应该是在以下四种情况下进行的分讨:

  • 全为 \(0\)
  • 除最低位为 \(1\) 外其他位全为 \(0\)
  • 全为 \(1\)
  • 除最低位为 \(0\) 外其他位全为 \(1\)

最终次数是 \(\left\lceil \log_2 B\right\rceil +2\) 的。

Code
#include<cstdio>
#include<vector>
#define qwq Ff472130
#define f(i,l,r) for (int i=l;i<=r;i++)

int main() {
	freopen("two.in","r",stdin);
	freopen("two.out","w",stdout);
	int B;scanf("%d",&B);
	f(i,0,5) {
		unsigned long long ret=0;
		f(j,0,B-1) if ((j>>i)&1) ret|=(1ull<<j);
		printf("%llu ",ret);
	}
	unsigned long long r1=0,r2=0;
	f(i,0,B-1) {
		int t=0;
		f(j,0,5) if ((i>>j)&1) t^=1;
		if (t) r1|=(1ull<<i);
		else r2|=(1ull<<i);
	}
	printf("%llu %llu\n",r1,r2);
	return 0;
}

但这样就结束了吗?并没有,大神 Hukaidi8566 做到了更优秀的方案,可以解决 \(B=70\) 的问题。

我们考虑对于两个位置 \(i,j\),我们通过两个数的对应位置来将这两个数缩减到一个数的规模。

有效的询问为 \((0,1)\)\((1,0)\),因为分讨发现 \((0,0)\)\((1,1)\) 并不能区分出这两个位置上的值。

   (i,j)=(0,0) (0,1) (1,0) (1,1)
(0,1)    00/01  01   00/11 01/11
(1,0)    00/10 00/11  10   10/11

依旧使用一个类似于真值表的东西来讨论不同的情况,根据上面 \((i,j)\) 可能值对 \((1,0)\)\((0,1)\) 进行运算可能得到的结果,大致可以分为以下几种情况:

  • 若两次分别返回 \((0,0),(0,0)\),则 \(i=0,j=0\)
  • \((0,0),(1,0)\),则 \(j=0\)
  • \((0,1),(0,0)\),则 \(i=0\)
  • \((0,1),(1,0)\),则 \(i=j\)
  • \((0,1),(1,1)\),则 \(j=1\)
  • \((1,1),(1,0)\),则 \(i=1\)
  • \((1,1),(1,1)\),则 \(i=1,j=1\)

我们发现,每种情况都至少能把问题规模减半,那么如果对于每组 \(i,j\),都存在两个数,使得对应的 \(i,j\) 位置上分别为 \((0,1)\)\((1,0)\),那么这个问题的规模就会变成二选一,所以我们考虑如何构造这 \(8\) 个数使其满足上述条件。

首先一个显然的思路是分治,和上面二进制分组时 \(B=16\) 的分治结构完全一样,每层使 \([l,mid]\) 中的数与 \([mid+1,r]\) 中的数都存在 \((0,1)\)\((1,0)\) 这两个结构,每层需要构造两个数,所以和上面一样只能解决 \(B=16\) 的规模。

一个优化是换成三进制,我们上面实际上在做一个以 \(10\) 为基础的复制和拉长操作,使其变成 \(10101010\)\(11110000\) 类似的结构,那么我们考虑用一个 \(3\times 3\) 的矩阵,同样进行拉长和复制操作,即可解决 \(B=18\) 规模的问题,矩阵如下,实际上取 \(e\) 时最优,但是只能取整数。

100
010
001

这个做法到这里基本上就已经到头了,没有优化空间了,这个算法之所以劣,是因为它对于一些相差很多位的两个位置 \((i,j)\),进行了多次比较而导致的,比如 \(0\)\(2^B-1\),在分治的每一层都进行了一次 \((0,1)\)\((1,0)\) 的比较,这完全是不必要的。

怎么优化呢,似乎没什么头绪,于是尝试用 \(4\) 个数来进行 \(6\) 个位置的比较,交给爆搜可以得到一组合法的解如下。

001011
010101
100110
111000

同样进行复制和拉长操作,即可解决 \(B=36\) 的规模。

注意到,这个方案实际上就是 \(\binom{4}{2}\),我们通过复制和拉长操作使他能解决的问题规模变成原来的平方,那么我们考虑 \(8\) 个数是不是就可以解决 \(B=\binom{8}{4}\) 的问题。

考虑所有 \(\binom{8}{4}\) 的方案,选的位置为 \(1\) 不选的位置为 \(0\),将每种方案竖着写出来,再将所有方案横向拼接,最终我们得到了一个 \(8\)\(\binom{8}{4}\) 的矩阵,将每行看作一个二进制数,就得到了 \(8\) 个二进制数,那么这 \(8\) 个数能否满足对于任意 \(i,j\),都存在两个数使得位置 \((i,j)\) 分别为 \((0,1)\)\((1,0)\) 呢?

事实上,考虑第 \(i\) 和第 \(j\) 列的 \(8\) 个数,由于组合的方案不同,所以必然存在一组 \((1,0)\)\((0,1)\) 结构,而由于 \(0\) 的个数与 \(1\) 的个数相同,所以能够找到两个位置分别为 \((1,0)\)\((0,1)\) 结构,对于不同的 \(B\),只需要取出这 \(8\) 个二进制数的前 \(B\) 位即可。

那么这个题就做完了,最大可以做到 \(B=70\),与官方题解的不同之处在于,这个方法以 \((0,1)\)\((1,0)\) 结构为基础,通过分析浪费的原因找到更优秀的方法来分配给不同的数 \(0\)\(1\),从而做到了更优秀的复杂度。

打出这 \(8\) 个数的代码如下。

Code
#include<cstdio>
#define ull unsigned long long
#define f(i,l,r) for (int i=l;i<=r;i++)

int t=0;
ull a[9],res[9];

inline void dfs(int now,int sum) {
	if (now==8) {
		if (sum==4&&t<64) {f(i,0,7) res[i]|=(a[i]<<t);t++;}
		return;
	}
	a[now]=0;dfs(now+1,sum);
	a[now]=1;dfs(now+1,sum+1);
}

int main() {
	freopen("in.in","w",stdout);
	dfs(0,0);
	f(i,0,7) printf("%llu ",res[i]);
	return 0;
}

提交代码如下,当然也可以直接在提交代码里搜出这 \(8\) 个数。

Code
#include<cstdio>
unsigned long long r[8]={18446744039349813248,18410715311050293248,35993646973091808,16174712728562728478,2037628642859119069,10602831876252421563,5373545155107072887,2704804894683666159};

int main() {
	freopen("two.in","r",stdin);
	freopen("two.out","w",stdout);
	int B;scanf("%d",&B);B=64-B;
	for (int i=0;i<=7;i++) printf("%llu ",(r[i]<<B)>>B);
	return 0;
}
posted @ 2026-07-16 17:02  Ff472130  阅读(4)  评论(0)    收藏  举报