位运算 - 状态压缩 - Compatible Numbers - CodeForces - 165E
题意:
给 定 一 个 长 度 为 n 的 序 列 , 对 序 列 中 的 每 一 个 数 a i , 在 序 列 中 查 询 是 否 存 在 a j 使 得 a i & a j = 0 , 对 每 个 a i , 输 出 一 个 对 应 a j 即 可 , 若 不 存 在 , 则 输 出 − 1 。 给定一个长度为n的序列,对序列中的每一个数a_i,在序列中查询是否存在a_j使得a_i\&a_j=0,\\对每个a_i,输出一个对应a_j即可,若不存在,则输出-1。 给定一个长度为n的序列,对序列中的每一个数ai,在序列中查询是否存在aj使得ai&aj=0,对每个ai,输出一个对应aj即可,若不存在,则输出−1。
Examples:
Input:
2
90 36
Output:
36 90
Input:
4
3 6 3 6
Output:
-1 -1 -1 -1
Input:
5
10 6 9 8 2
Output:
-1 8 2 2 8
数据范围:
1 < = n < − 1 0 6 , 1 < = a i < = 4 × 1 0 6 . T i m e l i m i t : 4000 m s , M e m o r y l i m i t : 262144 k B 1<=n<-10^6,1<=a_i<=4×10^6.\\Time\ limit:4000 ms,Memory\ limit:262144 kB 1<=n<−106,1<=ai<=4×106.Time limit:4000ms,Memory limit:262144kB
分析:
根 据 & 运 算 的 性 质 , 对 于 一 个 二 进 制 数 a , 要 使 得 a & b = 0 , 那 么 仅 需 将 a 的 1 都 和 0 相 与 , a 的 0 对 应 的 位 置 可 任 意 取 。 根据\&运算的性质,对于一个二进制数a,要使得a\&b=0,那么仅需将a的1都和0相与,a的0对应的位置可任意取。 根据&运算的性质,对于一个二进制数a,要使得a&b=0,那么仅需将a的1都和0相与,a的0对应的位置可任意取。
比 如 a = 10100 , 则 b = 0 × 0 × × 。 比如a=10100,则b=0×0××。 比如a=10100,则b=0×0××。
用 一 个 数 组 f , 对 输 入 的 元 素 对 应 的 元 素 做 一 个 映 射 。 用一个数组f,对输入的元素对应的元素做一个映射。 用一个数组f,对输入的元素对应的元素做一个映射。
我 们 可 以 先 做 最 极 端 的 情 况 , 把 所 有 的 1 变 成 0 , 0 变 成 1 , 又 因 为 原 本 是 0 的 位 置 可 以 取 任 意 值 , 原 本 是 1 的 位 置 必 须 变 0 , 这 样 就 会 有 部 分 合 法 状 态 被 遗 漏 , 是 因 为 原 本 是 0 的 位 置 可 能 仍 然 是 0 , 但 是 我 们 都 取 1 了 , 然 后 我 们 从 大 到 小 开 始 查 找 所 有 可 能 的 状 态 , 对 每 个 状 态 , 我 们 将 其 的 某 个 零 位 置 1 , 置 1 后 若 能 得 到 之 前 已 经 被 标 记 过 的 状 态 , 说 明 该 状 态 与 之 前 的 状 态 对 应 的 是 同 一 个 元 素 , 将 该 状 态 也 映 射 到 对 应 元 素 。 我们可以先做最极端的情况,把所有的1变成0,0变成1,\\又因为原本是0的位置可以取任意值,原本是1的位置必须变0,\\这样就会有部分合法状态被遗漏,是因为原本是0的位置可能仍然是0,但是我们都取1了,\\然后我们从大到小开始查找所有可能的状态,对每个状态,我们将其的某个零位置1,\\置1后若能得到之前已经被标记过的状态,说明该状态与之前的状态对应的是同一个元素,将该状态也映射到对应元素。 我们可以先做最极端的情况,把所有的1变成0,0变成1,又因为原本是0的位置可以取任意值,原本是1的位置必须变0,这样就会有部分合法状态被遗漏,是因为原本是0的位置可能仍然是0,但是我们都取1了,然后我们从大到小开始查找所有可能的状态,对每个状态,我们将其的某个零位置1,置1后若能得到之前已经被标记过的状态,说明该状态与之前的状态对应的是同一个元素,将该状态也映射到对应元素。
注意: 对 于 元 素 a i , 将 a i 与 ( 1 < < 23 ) − 1 异 或 , 因 为 a i < ( 1 < < 23 ) − 1 , 故 结 果 必 然 是 大 于 a i 的 。 因 此 , 再 枚 举 与 a i 对 应 的 其 他 状 态 时 , 需 要 从 大 到 小 枚 举 。 对于元素a_i,将a_i与(1<<23)-1异或,因为a_i<(1<<23)-1,故结果必然是大于a_i的。\\\qquad因此,再枚举与a_i对应的其他状态时,需要从大到小枚举。 对于元素ai,将ai与(1<<23)−1异或,因为ai<(1<<23)−1,故结果必然是大于ai的。因此,再枚举与ai对应的其他状态时,需要从大到小枚举。
具体落实:
① 、 用 一 个 全 1 的 二 进 制 数 与 a i 异 或 , 将 a i 的 所 有 位 上 的 数 取 反 。 ①、用一个全1的二进制数与a_i异或,将a_i的所有位上的数取反。 ①、用一个全1的二进制数与ai异或,将ai的所有位上的数取反。
② 、 从 大 到 小 枚 举 其 他 可 能 与 a i 对 应 的 状 态 , 将 其 也 映 射 到 a i 。 ②、从大到小枚举其他可能与a_i对应的状态,将其也映射到a_i。 ②、从大到小枚举其他可能与ai对应的状态,将其也映射到ai。
③ 、 对 每 个 a i , 查 询 数 组 f , 若 f [ a i ] 存 在 , 说 明 a i 对 应 的 a j 存 在 在 序 列 当 中 , 输 出 即 可 。 ③、对每个a_i,查询数组f,若f[a_i]存在,说明a_i对应的a_j存在在序列当中,输出即可。 ③、对每个ai,查询数组f,若f[ai]存在,说明ai对应的aj存在在序列当中,输出即可。
代码:
#include<cstdio>
#include<algorithm>
#include<cstring>
#include<iostream>
#include<vector>
using namespace std;
const int N=1e6+10;
int n,a[N],f[(1<<23)];
int main()
{
cin>>n;
for(int i=0;i<n;i++)
{
scanf("%d",&a[i]);
f[a[i]^(1<<23)-1]=a[i];
}
for(int i=(1<<23)-1;i;i--)
if(!f[i])
for(int j=0;j<23;j++)
if(f[i|(1<<j)])
{f[i]=f[i|(1<<j)];break;}
for(int i=0;i<n;i++)
if(f[a[i]])
printf("%d ",f[a[i]]);
else printf("-1 ");
return 0;
}