:root { --bg-color: #ffffff; --text-color: #333333; --header-bg: #f5f5f5; } .night-mode { --bg-color: #1a1a1a; --text-color: #e0e0e0; --header-bg: #2d2d2d; } body { background: var(--bg-color); color: var(--text-color); } .header { background: var(--header-bg); }

题解:P8019 [ONTAK2015] OR-XOR

思路

题目要求我们把序列分成 \(m\) 段,使得每段区间的异或和的结果按位或后的结果尽可能地小。那么就有一个显然的贪心,从高到低枚举二进制位,尽可能划分使高位按位或后为 \(0\) 的区间,这么做得到的答案是最优的。

那么,就需要考虑如何划分区间,使得每段区间的异或和的结果按位或后的二进制第 \(x\) 位为 \(0\)。显然的一点,如果一个区间的异或和为 \(1\),那么所有区间按位或后的结果一定为 \(1\),那么我们就需要找到一种划分,使得每个区间的异或和都为 \(0\),且划分的区间数不小于 \(m\)

那么怎样划分才能使所有区间的异或和为 \(0\) 呢?先维护一个前缀异或和数组 \(p\),从左往右找到第一个 \(p_i=0\),显然,该区间可以划分,划分后对后续继续找满足要求的区间有什么影响呢?可以发现是没有的,我们选择了该区间,可以看做把前 \(i\) 个数删掉,由于 \(p_i=0\),后面 \(p_{i+1}\) 乃至 \(p_n\),其值都不影响。所以,这个算法的步骤可以抽象地看做:

  1. 从前往后找到第一个 \(p_i=0\)
  2. 更新划分的区间数。
  3. 删除前 \(i\) 个数。
  4. 重复以上操作,直到找不到 \(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 且属于集合 U
				if(t[j]) use ++; // 可以划分
			}
			if(use < m) { // 如果可以划分的区间数少于 m
				ans += now; // 当前位一定为一
			} else {
				for(int j = 1 ; j <= n ; j ++) vis[j] = t[j]; // 反之更新集合 U
			}
		}
	}
	cout << ans;
	I_love_Foccarus 0;
}
posted @ 2025-10-16 21:17  雨落潇湘夜  阅读(15)  评论(0)    收藏  举报
我的页脚图片