题解 CF1742G

题目描述:

给你一个序列 \(A\),要求将 \(A\) 重新排序,使得序列 \(A\) 的前缀或和序列 \(B\) 的字典序最大。

题目分析:

这道题我们首先考虑一个性质,就是前缀或和序列 \(B\) 总是存在一个位置 \(j\),使得 \(j\) 前面形成的子序列单调递增,\(j\) 及其后面形成的子序列保持不变。

此时,我们根据上面的性质,不难得出如下结论:

  1. \(j\) 前面的序列我们只需要保证它们或起来是不断变大的就行
  2. \(j\) 后面的序列我们可以随便乱放,因为此时它们不改变或起来的值。

那么,这个神奇的位置 \(j\) 应该为多少呢,根据上述结论,我们不难看出,\(j\) 的最大值应为 \(\lceil \lg a_{\max} \rceil\),这一条结论的证明是显然的,理由如下:

因为算术操作“按位或”是没有进位的,且保证两个数按位或起来一定大于等于原先的两个数,故两个数按位或起来,其结果的二进制位数取决于两个数中较长的那个数的位数。并且,我们无法找出一个二进制位数比另个一大但数值比另一个小的两个数。所以我们可以证明,序列 \(A\) 中的最大值 \(a_{\max}\) 的二进制位数,就是我们要找的 \(j\) 的最大值。

然后就是对于 \(b_1\) 的讨论了,如果我们想要保证其字典序最大,则我们就需要保证 \(0 \operatorname{or} a_i\) 最大。我们运用大眼观察法,不难看出,\(b_1\) 的值为 \(a_{\max}\)

根据上述内容,我们不难得出以下贪心算法:

  1. 对原数组进行降序排序,将降序排序后的最大值(也就是 \(a_1\) )直接加入答案中。
  2. 循环 \(\lceil \lg a_{\max} \rceil\) 次,每次遍历序列 \(A\),将此时 \((now \operatorname{or} a_i)_{\max}\) 中的 \(a_{\max}\) 加入答案,并将 \(a_{\max}\) 删除(也就是标记为已使用)。
  3. 将剩下还没有删除的 \(a_i\) 直接加入到答案中。

此时,我们不难看出,该方法的时间复杂度为 \(O(n\lg a)\),可以通过此题。

代码实现:

#include <bits/stdc++.h>
#define dbg(x) cerr<<#x<<": "<<x<<endl;
#define int long long
#define MAX_SIZE (int)2e5
using namespace std;

signed main() {
    ios::sync_with_stdio(false);
#ifdef LOCAL
    freopen("in.in", "r", stdin);
    freopen("out.out", "w", stdout);
    double c1 = clock();
#endif
    //============================================
    int T;
    cin >> T;

    while (T--) {
        int n;
        cin >> n;
        int a[MAX_SIZE] = {};

        for (int i = 1; i <= n; i++)
            cin >> a[i];

        sort(a + 1, a + 1 + n, [](int a, int b) {
            return a > b;
        });
        vector <int> ans;
        unsigned long long now = a[1];
        ans.push_back(a[1]);
        a[1] = -1;

        for (int i = 30; i >= 1; i--) {
            unsigned long long maxnow = now;
            int nowpos = 1;

            for (int j = 2; j <= n; j++) {
                if (maxnow < (now | a[j]) && a[j] != -1) {
                    nowpos = j;
                    maxnow = now | a[j];
                }
            }

            if (a[nowpos] != -1) {
                now = maxnow;
                ans.push_back(a[nowpos]);
                a[nowpos] = -1;
            }
        }

        for (int i = 2; i <= n; i++)
            if (a[i] != -1)
                ans.push_back(a[i]);

        for (auto v : ans)
            cout << v << ' ';

        cout << endl;
    }

    //============================================
#ifdef LOCAL
    double c2 = clock();
    cerr << "Used Time: " << c2 - c1 << "ms" << endl;

    if (c2 - c1 > 1000)
        cerr << "Warning!! Time Limit Exceeded!!" << endl;

    fclose(stdin);
    fclose(stdout);
#endif
    return 0;
}
posted @ 2023-02-16 16:46  Larry76  阅读(21)  评论(0)    收藏  举报