【位运算 暴力】CF1742G Orray

· · 题解

CF1742G Orray

Description

给定一个数列 a,要求对 a 重排,使得重排后 a 的前缀或和数组 b 字典序最大。

这里前缀或和的定义是:b_i = a_1 \operatorname{or} a_2 \operatorname{or} \dots \operatorname{or} a_i,其中 \operatorname{or} 表示二进制按位异或操作。

### Analysis 首先考虑暴力怎么做: 为了保证字典序最大,$b_1$ 一定是 $a$ 中最大的数,然后依次确定 $b_2, b_3, \dots b_n$。想要确定 $b_i$,只需要枚举还没有选择的 $a_j$,使得 $b_i = b_{i - 1} \operatorname{or} a_j$ 最大 即可。 考虑一个显然的结论:一定存在一个 $j$,使得 $b_1 < b_2 < b_3 < \dots < b_j = b_{j + 1} = b_{j + 2} = \dots = b_n$。这就是说,$b$ 数列的前半部分一定互不相同,后半部分一定相同,证明留做习题。 考虑如果 $b_i > b_{i - 1}$,那么 $b_i$ 的二进制一定比 $b_{i - 1}$ 多至少一个 $1$(因为 $b_i$ 是由 $b_{i - 1}$ 或另一个数得到,$b_{i - 1}$ 在二进制下 $1$ 的位置在 $b_i$ 中一定也是 $1$)。而 $b_i$ 二进制只有 $\lceil\log_2 a\rceil = 30$ 位,所以 $b_i > b_{i - 1}$ 的位置最多只有 $30$ 个,结合上一段中的结论,这样的位置只会出现在 $b$ 的前 $30$ 位。 所以只需要通过第一段中的暴力方法确定 $b$ 中前三十位的位置,剩余位置均与 $b_{30}$ 相同。 考虑运算量:每组数据会进行 $\min(n, 30)$ 次枚举,每次枚举 $O(n)$,最坏情况下枚举了 $30 \Sigma n$ 次 $a_j$,可以接受。 ### Code ```cpp #include <vector> #include <iostream> #include <algorithm> typedef long long int ll; const int maxn = 200005; int a[maxn]; bool op[maxn]; std::vector<int> Ans; int main() { std::ios::sync_with_stdio(false); std::cin.tie(nullptr); int T, n; for (std::cin >> T; T; --T) { Ans.clear(); std::cin >> n; for (int i = 1; i <= n; ++i) op[i] = false; for (int i = 1; i <= n; ++i) { std::cin >> a[i]; } int ans = 0; for (int i = 1, lim = std::min(n, 30); i <= lim; ++i) { int tans = ans, pos = 0; for (int i = 1; i <= n; ++i) if (op[i] == false) { if ((ans | a[i]) >= tans) { pos = i; tans = ans | a[i]; } } ans = tans; op[pos] = true; Ans.push_back(pos); } for (int i = 1; i <= n; ++i) if (op[i] == false) Ans.push_back(i); for (auto &&u : Ans) { std::cout << a[u] << ' '; } std::cout << '\n'; } } ```