思路
题目要求我们把序列分成 \(m\) 段,使得每段区间的异或和的结果按位或后的结果尽可能地小。那么就有一个显然的贪心,从高到低枚举二进制位,尽可能划分使高位按位或后为 \(0\) 的区间,这么做得到的答案是最优的。
那么,就需要考虑如何划分区间,使得每段区间的异或和的结果按位或后的二进制第 \(x\) 位为 \(0\)。显然的一点,如果一个区间的异或和为 \(1\),那么所有区间按位或后的结果一定为 \(1\),那么我们就需要找到一种划分,使得每个区间的异或和都为 \(0\),且划分的区间数不小于 \(m\)。
那么怎样划分才能使所有区间的异或和为 \(0\) 呢?先维护一个前缀异或和数组 \(p\),从左往右找到第一个 \(p_i=0\),显然,该区间可以划分,划分后对后续继续找满足要求的区间有什么影响呢?可以发现是没有的,我们选择了该区间,可以看做把前 \(i\) 个数删掉,由于 \(p_i=0\),后面 \(p_{i+1}\) 乃至 \(p_n\),其值都不影响。所以,这个算法的步骤可以抽象地看做:
- 从前往后找到第一个 \(p_i=0\)。
- 更新划分的区间数。
- 删除前 \(i\) 个数。
- 重复以上操作,直到找不到 \(p_i=0\)。
然后我们就可以得到我们可以划分的最大区间数量,检查其是否等于或超过 \(m\),如果其小于 \(m\),第 \(x\) 位就一定为 \(1\),无法避免。以及若序列中第 \(x\) 位为 \(1\) 的 \(a_i\) 数量为奇数个,那么无论怎么划分,每段区间的异或和的结果按位或后的二进制第 \(x\) 位为一定 \(1\),即第 \(x\) 位就一定为 \(1\)。
仔细研究算法步骤,可以发现我们只不过在找二进制第 \(x\) 位满足 \(p_i=0\) 的 \(i\) 作为区间的右边界,所以我们直接枚举即可。
注意,高位确定的区间划分会影响低位的区间划分,我们用集合 \(U_x\) 表示第 \(x\) 位满足 \(p_i=0\) 的 \(i\) 的集合,即划分区间右边界的集合,那么对于 \(y<x\) 一定满足 \(U_y\subseteq U_x\),所以二进制第 \(x\) 位满足 \(p_i=0\) 的 \(i\) 要满足 \(i\in U_{x+1}\)。
所以,我们最终的算法为:从高到低枚举二进制位 \(x\),检查当前位 \(a_i=1\) 的数量是否为奇数,若为奇数,该位为 \(1\),答案算上 \(2^x\) 的贡献;若为偶数,检查当前位 \(p_i=0\) 且 \(i\in U_{x+1}\) 的 \(i\) 的数量是否小于 \(m\),若小于 \(m\),该位为 \(1\),答案算上 \(2^x\) 的贡献;反之,该位为 \(0\),更新集合 \(U_x\)。
至于如何维护集合 \(U\),我们直接通过维护标记数组即可。
代码
#include<bits/stdc++.h>
#define int long long
#define I_love_Foccarus return
#define cin_fast ios::sync_with_stdio(false) , cin.tie(0) , cout.tie(0)
#define endl '\n'
//#define getchar getc
#define pii pair<int,int>
#define mk(a,b) make_pair(a,b)
#define fi first
#define se second
#define pd(a) push_back(a)
#define in(a) a = read_int()
using namespace std;
const int Size = 1 << 14;
const int N = 5e5 + 5;
const int inf = 0x3f3f3f3f;
const long long INF = 0x3f3f3f3f3f3f3f3f;
inline char getc(){static char syn[Size] , *begin = syn , *end = syn;if(begin == end) begin = syn , end = syn + fread(syn , 1 , Size , stdin);I_love_Foccarus *begin ++;
}
inline int read_int() {int x = 0;char ch = getchar();bool f = 0;while('9' < ch || ch < '0') f |= ch == '-' , ch = getchar();while('0' <= ch && ch <= '9') x = (x << 3) + (x << 1) + ch - '0' , ch = getchar();I_love_Foccarus f ? -x : x;
}
int a[N] , vis[N] , t[N];signed main() {//cin_fast;int n , m;in(n) , in(m);for(int i = 1 ; i <= n ; i ++) {in(a[i]);vis[i] = 1; // 集合 U 最初包含所有 i}int ans = 0;for(int i = 63 ; i >= 0 ; i --) { // 从高到低枚举二进制位int now = (1ll << i) , h = 0 , use = 0;for(int j = 1 ; j <= n ; j ++) {if(a[j] & now) h ++; // 统计该位为 1 的数量}if(h & 1) ans += now; // 若为奇数,当前位一定为一else {h = 0;for(int j = 1 ; j <= n ; j ++) {h ^= a[j]; // 前缀异或和t[j] = vis[j] & (!(h & now)); // 如果前缀异或和为 0 且属于集合 Uif(t[j]) use ++; // 可以划分}if(use < m) { // 如果可以划分的区间数少于 mans += now; // 当前位一定为一} else {for(int j = 1 ; j <= n ; j ++) vis[j] = t[j]; // 反之更新集合 U}}}cout << ans;I_love_Foccarus 0;
}