题意简述

从 1 到 n 中选择一些数字,将选中的数字乘 2,使修改后所有数字的异或和不小于原序列按位或的 2 倍。

tag:计数 动态规划

难度cf1250 洛谷普及

思考

设 n 的最高位下标为 d。因为 1 到 n 中包含 2^d,并且低于第 d 位的每一位都能在某个数中出现,所以:

$$1 \mid 2 \mid \cdots \mid n = 2^{d+1}-1 = 111\cdots111_2$$

记这个按位或的结果为 k。

因此,我们需要让最终异或和至少为 2k。由于:

$$2k = 2^{d+2}-2 = 111\cdots110_2$$

所以目标是让最终异或和的第 d+1 位到第 1 位全部为 1,最低位无特殊要求。

贪心

先计算当前异或和 $ans = 1 \oplus 2 \oplus \cdots \oplus n$,然后从高位向低位处理。

假设当前处理第 i+1 位:

  • 如果这一位已经是 1,不需要操作;

  • 如果这一位是 0,就选择 2^i,将它乘 2 变成 2^{i+1}。

这次操作会使异或和变为:

$$ans’ = ans \oplus 2^i \oplus 2^{i+1}$$

因此只会改变第 i 位和第 i+1 位。当前第 i+1 位会被翻转为 1,而更高位不会受到影响。

继续从高位处理到低位,就能保证已经处理好的高位不会被破坏,最终得到满足条件的异或和。

复杂度

计算初始异或和为 O(n),按位贪心为 O(log n),总复杂度为 O(n)。

代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
typedef __int128 lll;
typedef pair<ll, ll> PLL;
typedef pair<int, int> PII;
typedef tuple<int, int, int> TII;
#define endl '\n'
const ll N = 2e5 + 5;
const ll INF = 1e18, MINF = -1e18;

void solve()

{

    ll n;
    cin >> n;



    ll ans = 0;
    vector<ll> aa;

    for (int i = 1; i <= n; i++)

    {
        ans ^= i;
    }

    for (int i = n; i >= 1; i--)

    {
        bool flag = (i & (i - 1)) == 0;

        if (flag)
        {
            if ((ans ^ i ^ (2 * i)) > ans)

            {
                aa.push_back(1);
                ans = ans ^ i ^ (2 * i);
            }

            else
            {
                aa.push_back(0);
            }
        }

        else
        {
            aa.push_back(0);
        }

    }

    reverse(aa.begin(), aa.end());
    for (auto v : aa)
    {
        cout << v << ' ';
    }
    cout << endl;
}

int main()
{
    ios::sync_with_stdio(false);
    cin.tie(0);

    ll t = 1;
    cin >> t;
    while (t--)
    {
        solve();
    }

    return 0;

}