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;
}

浙公网安备 33010602011771号