当前位置: 首页 > news >正文

题解: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 且属于集合 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;
}
http://www.hskmm.com/?act=detail&tid=32595

相关文章:

  • DP 思维好题(转载)
  • python sse的是什么?
  • idea代码阿里格式化
  • 万字长文详述单据引擎原理、流程、单据管理 - 智慧园区
  • windows 链接共享打印机出现错误0x00000709?打印机0x0000011b错误?0x0000bcd、0x00000709、0x00000011b
  • 解码Linux文件IO目录检索与文件属性
  • p66实验题
  • 【比赛记录】2025NOIP 冲刺模拟赛合集I
  • 20251016
  • C# - 串口助手
  • 使用SpringBoot+MyBatisPlus实现增删改查
  • P4168 [Violet] 蒲公英题解
  • Java了解
  • VGG使用块的网络
  • 使用SpringBoot + Thymeleaf + MyBatisPlus实现一个简单的书籍管理系统
  • Linux 基础
  • P2605 [ZJOI2010] 基站选址
  • NVIDIA Jetson AGX Xavier刷机教程
  • 洛谷p1462-通往奥格瑞码道路
  • 详细介绍:VR 太阳光参数与快速渲染
  • 位运算中没用的小技巧
  • 第六周
  • 超越基础:SightAI 智能路由与多模型选择实战 - sight
  • [Vulhub靶机]JARBAS靶机渗透
  • 10月16号
  • CF622D 题解
  • vue学习的总结
  • 最小二乘问题详解5:非线性最小二乘求解实例
  • AlexNet
  • 【28】C# WinForm入门到精通 ——多文档窗体MDI【属性、强大的方法、实例、源码】【多窗口重叠、水平平铺、垂直平铺、窗体传值】