1 条题解
-
0
题目理解
我们有 种可能的观点( 位二进制数), 个成员围坐在圆桌旁,每人有一个独特的观点(0 到 之间的整数)。
要将圆桌上的成员分成两个连续的小组,使得每个小组都涵盖所有观点。具体来说:
对于每个问题(二进制位),如果一组中有人该位为 0,另一组中也必须有人该位为 0;同样,如果一组中有人该位为 1,另一组中也必须有人该位为 1。
换句话说:每个小组自身必须包含所有 个比特位的两种取值(0 和 1)。
关键观察
1. 覆盖条件的等价表述
对于一个成员集合 ,定义:
- : 中所有数的按位或
- : 中所有数的按位与
定理:集合 满足"对于每个比特位,既包含 0 又包含 1" 当且仅当:
证明:
- 对于某个比特位 :
- 如果 中既有 0 又有 1:
- 的第 位 = 1(因为有 1)
- 的第 位 = 0(因为有 0)
- 异或结果 = 1
- 如果 中只有 0 或只有 1:
- 和 的第 位相同
- 异或结果 = 0
- 如果 中既有 0 又有 1:
- 因此, 的第 位为 1 当且仅当该位在 中既有 0 又有 1。
- 要所有位都满足,就需要异或结果等于 。
2. 环形结构处理
由于是圆桌,我们可以:
- 将原数组复制一份接在后面(破环成链)
- 在长度为 的数组上寻找所有有效的划分
3. 划分的性质
在圆桌上,一个划分就是将 个人分成两个连续段。由于是环形,有 种可能的划分方式(在链上就是 个可能的切分点)。
算法设计
暴力方法(不可行)
枚举所有 种划分,对每个划分检查两个小组是否都满足条件。
检查一个小组需要 时间,总复杂度 ,对于 最大 不可行。高效算法:双指针 + 位运算
核心思路:
- 破环成链,将数组复制:
arr[0..m-1]→arr[0..2m-1] - 对于每个起始位置
i(0 ≤ i < m),用双指针找到所有以i为左小组起点的有效划分 - 使用滑动窗口维护 OR 和 AND 值
具体步骤:
步骤 1:预处理覆盖信息
对于链上的每个位置
i,我们想知道:- 从
i开始的最短连续段(向右延伸)满足覆盖条件 - 从
i结束的最短连续段(向左延伸)满足覆盖条件
这可以用双指针预处理。
步骤 2:双指针扫描
维护两个指针
j和k:j:使得[i, j]满足覆盖条件的最小右端点k:使得[j+1, k]满足覆盖条件的最小右端点
当
i增加时,j和k单调不减。步骤 3:计数有效划分
对于固定的
i,有效的划分位置p满足:i ≤ p < i + m - 1(不能空一组)[i, p]满足覆盖条件[p+1, i+m-1]满足覆盖条件
由于我们维护了
j和k,可以在 O(1) 时间内知道对于当前i有多少个有效的p。
详细实现
数据结构
arr[2*m]:破环成链后的数组pref_or[i],pref_and[i]:前缀 OR 和 ANDsuff_or[i],suff_and[i]:后缀 OR 和 AND
算法流程
- 破环成链:将原数组复制到
arr[0..2m-1] - 预处理覆盖数组:
- 对于每个起始点
i,找到最小的j使得[i, j]满足覆盖条件 - 同样预处理从右边开始的覆盖信息
- 对于每个起始点
- 双指针扫描:
- 初始化
j = k = 0 - 对于每个
i从 0 到 m-1:- 移动
j使得[i, j]满足条件 - 移动
k使得[j+1, k]满足条件 - 如果
k < i + m - 1且两个段都满足条件,计数+1 - 更新 OR 和 AND 值(删除
arr[i]的影响)
- 移动
- 初始化
- 输出结果
复杂度分析
- 时间复杂度:
- 双指针扫描,每个元素被加入和移除窗口各一次
- 位运算操作是 的
- 空间复杂度:
- 存储原数组和辅助数组
代码实现(C++ 风格伪代码)
#include <bits/stdc++.h> using namespace std; const int MAXM = 4000005; // 2*m 的最大值 int n, m; int arr[MAXM]; long long ans = 0; // 检查区间 [l, r] 是否满足覆盖条件 bool check_cover(int l, int r) { int or_val = 0, and_val = (1 << n) - 1; for (int i = l; i <= r; i++) { or_val |= arr[i]; and_val &= arr[i]; } return (or_val ^ and_val) == ((1 << n) - 1); } int main() { scanf("%d%d", &n, &m); for (int i = 0; i < m; i++) { scanf("%d", &arr[i]); arr[i + m] = arr[i]; // 破环成链 } // 双指针扫描 int j = 0, k = 0; int or1 = 0, and1 = (1 << n) - 1; // 第一段的 OR 和 AND int or2 = 0, and2 = (1 << n) - 1; // 第二段的 OR 和 AND for (int i = 0; i < m; i++) { // 移动 j 使得 [i, j] 满足条件 while (j < i + m - 1 && (or1 ^ and1) != ((1 << n) - 1)) { j++; or1 |= arr[j]; and1 &= arr[j]; } // 移动 k 使得 [j+1, k] 满足条件 if (j < i + m - 1) { k = max(k, j); or2 = 0; and2 = (1 << n) - 1; while (k < i + m - 1 && (or2 ^ and2) != ((1 << n) - 1)) { k++; or2 |= arr[k]; and2 &= arr[k]; } if (k < i + m - 1 && (or2 ^ and2) == ((1 << n) - 1)) { ans++; } } // 移除 arr[i] 的影响 // 这里需要更精细的维护,实际实现要用更复杂的数据结构 // 或者重新计算 OR 和 AND } printf("%lld\n", ans); return 0; }
样例解析
对于样例:
n=4, m=5 arr = [1, 10, 0, 11, 3]二进制表示:
- 1 = 0001
- 10 = 1010
- 0 = 0000
- 11 = 1011
- 3 = 0011
有效划分:
[1,10] | [0,11,3]- 第一组:0001, 1010 → 覆盖所有位
- 第二组:0000, 1011, 0011 → 覆盖所有位
[3,1,10] | [0,11]- 第一组:0011, 0001, 1010 → 覆盖所有位
- 第二组:0000, 1011 → 覆盖所有位
总结
这道题的关键在于:
- 将覆盖条件转化为位运算(OR ⊕ AND = 全1)
- 破环成链处理环形结构
- 使用双指针高效扫描所有可能划分
- 维护滑动窗口的位运算结果
- 1
信息
- ID
- 3663
- 时间
- 1000ms
- 内存
- 256MiB
- 难度
- 10
- 标签
- 递交数
- 1
- 已通过
- 1
- 上传者