1 条题解
-
0
思路分析
我们有一个目标集合 ,用 01 串给出。
允许的操作:
1 x:创建单点集合2 x y:合并两个不相交集合3 x y:将集合平移
关键观察:
- 如果我们能构造出某个集合 ,那么通过平移可以得到 。
- 如果 在某种“间隔”结构下是周期性的,我们可以用分治方法:把 分成左右两半,递归构造左半部分,然后平移得到右半部分,再合并。
分治构造法
设当前要构造的区间是 对应的集合 。
递归过程:
-
如果区间长度 :
- 如果该位置在目标集合中,创建一个单点集合。
- 否则,创建一个空集合(但题目不允许直接创建空集,所以需要在递归时处理空的情况,避免创建)。
-
否则,将区间分成两半 和 ,其中 。
-
递归构造左半部分 。
-
如果右半部分完全不在目标集合中,则直接返回 。
-
否则,将 平移 得到 。
-
合并 和 得到整个区间的集合。
问题:右半部分可能不是左半部分的精确平移,因为目标集合可能不对称。
解决:我们只构造目标集合中存在的元素,所以平移后得到的是右半部分中与左半部分“对应位置”存在的元素。但这样会漏掉右半部分中额外存在的元素吗?
不会,因为我们递归时,右半部分也会被递归构造,但这里我们直接平移左半部分来覆盖右半部分中与左半部分对称的部分,其余部分由递归补全?
这样想会复杂,更好的办法是:
更简单的分治构造
我们注意到,如果目标集合在某个区间 上可以表示为 的形式,那么我们可以先构造 ,再平移并合并。
一个经典方法:
设 是当前区间长度的一半,检查能否将集合分成两部分: 和 的不交并。
如果可以,就递归构造 ,然后平移 并合并。
如果不可以,就继续细分区间直到单点。但直接检查是否满足 比较麻烦,我们可以用另一种方式:
递归构造函数设计
我们定义函数
solve(l, r)返回一个操作编号,表示构造出了集合 。- 如果 中不存在目标元素,返回空(但空集不能直接创建,所以需要特殊处理——可以返回一个无效编号,调用者处理)。
- 如果区间长度 ,创建单点集合 ,返回它的编号。
- 否则令 。
- 递归得到左半部分 。
- 递归得到右半部分 。
- 如果 是空,返回 。
- 如果 是空,返回 。
- 否则,将 平移 得到 ,然后合并 和 得到最终集合。
但这样不对,因为平移 得到的是 中与 对应的位置,不一定等于 。
正确方法:利用二进制分组(位运算构造)
观察平移操作:如果我们能构造出所有形如“二进制掩码”的集合,那么任意集合可以通过这些掩码的并得到。
例如,构造 :
- 构造 (间隔3)
- 平移1得到
- 合并
更系统的做法:
将目标集合 按二进制位分治:- 设 是满足 的最大整数。
- 将 分成两部分:
向左平移 得到的集合 - 如果 非空,递归构造 ,然后对结果平移 得到 的原始位置,再与 合并。
- 如果 为空,直接返回 的构造。
这样保证每次递归区间长度减半,总操作数 。
实现细节
- 用全局数组记录操作序列。
- 用映射记录每个区间对应的操作编号。
- 递归时,如果当前区间集合为空,不创建任何操作,返回0(表示空集)。
- 合并时,如果一方为空,直接返回另一方。
- 保证所有操作集合不超出 。
代码实现(C++)
#include <bits/stdc++.h> using namespace std; int n; string s; vector<tuple<int, int, int>> ops; // (type, x, y) int cnt; // 构造集合 T ∩ [l, r],返回操作编号 int solve(int l, int r) { // 检查 [l, r] 是否全空 bool empty = true; for (int i = l; i <= r; i++) { if (s[i-1] == '1') { empty = false; break; } } if (empty) return 0; // 单点情况 if (l == r) { ops.emplace_back(1, l, 0); return ++cnt; } int mid = (l + r) / 2; int left_id = solve(l, mid); int right_id = solve(mid+1, r); // 如果一边为空 if (left_id == 0) return right_id; if (right_id == 0) return left_id; // 否则,将左半部分平移 (mid-l+1) 得到右半部分的对应部分 int shift = mid - l + 1; ops.emplace_back(3, left_id, shift); int shifted_id = ++cnt; // 合并 ops.emplace_back(2, shifted_id, right_id); return ++cnt; } int main() { ios::sync_with_stdio(false); cin.tie(0); cin >> n; cin >> s; cnt = 0; int root = solve(1, n); cout << ops.size() << "\n"; for (auto& [type, x, y] : ops) { if (type == 1) { cout << type << " " << x << "\n"; } else { cout << type << " " << x << " " << y << "\n"; } } return 0; }
复杂度分析
- 时间复杂度:
- 空间复杂度:
- 操作次数:,满足
- 总代价:,满足
总结
这道题的核心是分治构造,利用平移操作减少重复构造,通过递归将目标集合分解为更小的对称部分,从而有效控制操作次数和总代价。
- 1
信息
- ID
- 5572
- 时间
- 1000ms
- 内存
- 256MiB
- 难度
- 10
- 标签
- 递交数
- 12
- 已通过
- 1
- 上传者