JZOJ5952.
【NOIP2018模拟11.5A组】凯旋而归
简要题意
对于一个数列 ,一个函数 定义如下: 现在给你一个数列,依次求出其所有前缀的 的值。
策略分析
首先我们需要一些前置知识,我们在求一个数列的子段异或和的时候,可以像其普通加法求和一样处理出前缀和并
求解。那么对于异或前缀和数组
来讲,区间 的异或和就等于 。
知道了这个性质以后,我们就可以做出本题 50 分的做法,即对于每个位置
都去暴力枚举 或
区间(枚举这两个区间得到的答案是等价的)的最大答案。假设我们枚举到了位置
,那么我们要求的就是答案 就等于 。也就是说,想让答案尽可能大,就要让
尽可能大。于是我们自然而然地想到拆位来考虑。对于第 位(下标从
开始),这一位能够让整个数增加当且仅当这一位为 且能够被更改为 。也就是说如果第 位为 ,那么它变成 就可以对整个数造成 的贡献。那么对于一个形如 的式子来说,我们设 和
都只有一位(高位情况不过是拆开考虑),那么当 时,无论 为何值,最终得到的结果都是 ,不会对结果产生任何影响。但当 时,我们就要想办法让它变成
从而使得答案增加。那么我们的目的就变成了,找到一个 能够使其跟当前的 运算的结果尽可能的大。
那么我们就要考虑能不能往这一位填 ,这要求我们去判断在前缀数组的第
位之前有没有一个数可以和这个数运算使得我们期望的 位变成
且不去影响已经填好的,我们知道,只要我们把高位的一个 变成 ,那么低位的甭管怎么变都不可能比现在大,那么我们直接贪心地从高位往低位讨论就好了。问题就在于怎么去让高位已经填好的不变。这里我们定义一种二进制下的集合关系,即
,也就是说 中为 的位置在 中也必须为 。这样很抽象不好理解,我们举个例子。
1 2 3
| a = 111000101010 b = 110000101010 c = 100000101010
|
上面的三个二进制数中,。
定义了这个以后,我们定义一个数组 ,其中 表示包含 的数出现的位置的最小下标为 ,也就是存在一个数包含 且这个数在
里(当然也有可能不存在)最早出现的那个下标为 。如果这个下标不存在那么显然值就是无穷大。我们在处理异或前缀和的时候就要初始化这个数组,初始化的过程非常显然但不便于用语言描述所以直接给出:
1
| f[a[i]] = min(f[a[i]], i);
|
接下来我们要预处理这个数组,处理的时候从大的数向小的处理,这样可以一遍就找到我们想要的最小位置。因为本题数据范围是
,所以我们大概从 开始枚举即可,每次判断一下,如果
那么我们就可以用
去更新
的子集(这里的子集和前面的包含关系定义是一样的)内的所有元素,更新的方法就是逐位枚举,如果是
变成
就是它的子集内的元素,那么更新就好了,但是更新完了要记得把改成
的这一位再变回来更新下一位,实现过程如下:
1 2 3 4 5 6 7 8 9
| for (int i = (1 << 20) - 1;i >= 0; --i) { if(f[i] < INF) { for (int j = 0; j < 20; ++j) { if(i & p[j]) { f[i ^ p[j]] = min(f[i ^ p[j]], f[i]); } } } }
|
注:这里的 即表示
次方的值,这个值在我们这道题中用其另一性质,即其二进制表示下第 位为 ,其余均为 ,用一个数和 做与运算可以判断这个数第 位为 还是 。
预处理结束以后,我们就可以开始做了,逐个枚举前缀和,对于当前枚举到的
,我们用一个
来代表前面一半异或前缀和的值,那么我们仍然是从高位往低位枚举(基于贪心思想,高位为
的贡献高于低位为 的贡献。如果当前这一位是 ,那么我们就不用管它了,因为它不会再产生贡献。如果这一位为
,那么我们就看能不能找到一个小于
的位置上的元素使得其是 这一位变成
后的数的子集,只有这样才能保证操作完后以前操作的位上的值不会被改变,如果找到了就把
这位变成 ,相反的,如果没找到就变回 ,再去枚举下一位。枚举完后按照
这个式子的形式算一下输出就好了,这个式子在这里变成了 ,实现过程如下:
1 2 3 4 5 6 7 8 9
| for (int i = 1; i <= n; ++i) { int s = 0; for (int j = 19; j >= 0; --j) { if (!(a[i] & p[j]) && f[s | p[j]] <= i) { s |= p[j]; } } cout << s + (a[i] ^ s) << '\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
| #include <iostream> #include <cstdio> #include <cstring> using namespace std; namespace SHAWN { const int N=5e5 + 7, M = 2e6 + 7, INF = 1e9; int a[N], f[M], p[32]; int n; signed work() { cin >> n; memset(f, 0x7f, sizeof(f)); for (int i = 1; i <= n; ++i) { cin >> a[i]; a[i] ^= a[i - 1]; f[a[i]] = min(f[a[i]], i); } p[0] = 1; for (int i = 1; i < 20; ++i) { p[i] = p[i - 1] << 1; } for (int i = (1 << 20) - 1;i >= 0; --i) { if(f[i] < INF) { for (int j = 0; j < 20; ++j) { if(i & p[j]) { f[i ^ p[j]] = min(f[i ^ p[j]], f[i]); } } } } for (int i = 1; i <= n; ++i) { int s = 0; for (int j = 19; j >= 0; --j) { if (!(a[i] & p[j]) && f[s | p[j]] <= i) { s |= p[j]; } } cout << s + (a[i] ^ s) << '\n'; } return 0; } } signed main() { ios :: sync_with_stdio(false); cin.tie(nullptr); return SHAWN :: work(); }
|