1 条题解
-
0
好的,我来为这道题写一个详细的题解。
题目理解
我们有一个长度为 的 序列,初始全为 (没有脑洞)。支持三种操作:
- 挖脑洞:将区间 全部设为
- 脑洞治疗:用区间 中的 (正常组织)去填补区间 中的 (脑洞)
- 查询:询问区间 中最长的连续 的长度
问题分析
1. 操作特点
- 操作 0:区间赋值为 → 区间覆盖操作
- 操作 2:查询最长连续 → 区间信息维护
- 操作 1:最复杂,需要:
- 统计 中有多少个
- 将 全部设为
- 用这些 去填补 中的脑洞(从左到右填)
数据结构选择
由于 ,我们需要 或 复杂度的算法。
线段树是合适的选择,需要维护:
对于每个区间,存储:
- : 的数量
- :区间长度
- :从左开始的最长连续 ,从右开始的最长连续 ,整个区间的最长连续
- :同理对于
- :懒标记( 无操作, 全设 , 全设 )
线段树节点设计
struct Node { int sum; // 1的数量 int len; // 区间长度 // 对于0的连续信息 int lmax0, rmax0, max0; // 对于1的连续信息 int lmax1, rmax1, max1; int lazy; // -1:无, 0:全0, 1:全1 };
信息合并
合并两个区间 和 :
void merge(Node &res, const Node &l, const Node &r) { res.sum = l.sum + r.sum; res.len = l.len + r.len; // 合并0的连续信息 res.lmax0 = (l.lmax0 == l.len) ? l.len + r.lmax0 : l.lmax0; res.rmax0 = (r.rmax0 == r.len) ? r.len + l.rmax0 : r.rmax0; res.max0 = max({l.max0, r.max0, l.rmax0 + r.lmax0}); // 合并1的连续信息 res.lmax1 = (l.lmax1 == l.len) ? l.len + r.lmax1 : l.lmax1; res.rmax1 = (r.rmax1 == r.len) ? r.len + l.rmax1 : r.rmax1; res.max1 = max({l.max1, r.max1, l.rmax1 + r.lmax1}); }
操作实现
1. 挖脑洞(操作 0)
区间覆盖为 :
void update_range0(int u, int l, int r, int ql, int qr) { if (ql <= l && r <= qr) { apply_tag(u, 0); // 全设为0 return; } push_down(u); int mid = (l + r) >> 1; if (ql <= mid) update_range0(u<<1, l, mid, ql, qr); if (qr > mid) update_range0(u<<1|1, mid+1, r, ql, qr); push_up(u); }2. 查询最长连续0(操作 2)
Node query_max0(int u, int l, int r, int ql, int qr) { if (ql <= l && r <= qr) return tree[u]; push_down(u); int mid = (l + r) >> 1; if (qr <= mid) return query_max0(u<<1, l, mid, ql, qr); if (ql > mid) return query_max0(u<<1|1, mid+1, r, ql, qr); Node left = query_max0(u<<1, l, mid, ql, qr); Node right = query_max0(u<<1|1, mid+1, r, ql, qr); Node res; merge(res, left, right); return res; }3. 脑洞治疗(操作 1)
这是最复杂的操作,分三步:
void treatment(int l0, int r0, int l1, int r1) { // 1. 统计[l0, r0]中1的数量 int cnt1 = query_sum(1, 1, n, l0, r0); // 2. 将[l0, r0]全部设为0 update_range0(1, 1, n, l0, r0); if (cnt1 == 0) return; // 没有可用组织 // 3. 在[l1, r1]中填补cnt1个1(从左到右) fill_ones(1, 1, n, l1, r1, cnt1); }
关键操作:fill_ones
在区间 中从左到右填充 个 :
int fill_ones(int u, int l, int r, int ql, int qr, int cnt) { if (cnt == 0) return 0; if (ql <= l && r <= qr) { int zeros = (r - l + 1) - tree[u].sum; // 当前区间0的数量 if (zeros <= cnt) { // 可以填满整个区间 apply_tag(u, 1); return zeros; } } push_down(u); int mid = (l + r) >> 1; int filled = 0; // 先填左子区间 if (ql <= mid) { filled += fill_ones(u<<1, l, mid, ql, qr, cnt); cnt -= filled; } // 再填右子区间 if (cnt > 0 && qr > mid) { filled += fill_ones(u<<1|1, mid+1, r, ql, qr, cnt); } push_up(u); return filled; }
复杂度分析
- 操作 0:区间覆盖,
- 操作 2:区间查询,
- 操作 1:统计 + 覆盖 + 填充
- 总复杂度:,对于 可接受
样例验证
输入:
10 10 0 2 2 0 4 6 0 10 10 2 1 10 1 8 10 1 4 2 1 10 1 1 4 8 10 2 1 10 1 7 10 1 6 2 1 10逐步分析:
- 初始:全1
[1,1,1,1,1,1,1,1,1,1] - 挖2-2:
[1,0,1,1,1,1,1,1,1,1] - 挖4-6:
[1,0,1,0,0,0,1,1,1,1] - 挖10-10:
[1,0,1,0,0,0,1,1,1,0] - 查询1-10:最长连续0是4-6,长度3 → 输出3
- 治疗:用8-10修补1-4
- 8-10有2个1(位置8,9)
- 填补1-4:位置1,3已为1,填补2,4 →
[1,1,1,1,0,0,1,0,0,0]
- 查询1-10:最长连续0是8-10,长度3 → 输出3
- 治疗:用1-4修补8-10
- 1-4有4个1
- 填补8-10:全部填补 →
[0,0,0,0,0,0,1,1,1,1]
- 查询1-10:最长连续0是1-6,长度6 → 输出6
- 治疗:用7-10修补1-6
- 7-10有4个1
- 填补1-6:只能填前4个 →
[1,1,1,1,0,0,0,0,0,0]
- 查询1-10:最长连续0是5-10,长度6 → 输出6
输出:
3 3 6 6✓
总结
本题的关键在于:
- 设计合适的线段树节点,维护连续0和连续1的信息
- 实现复杂的大脑治疗操作,特别是从左到右的填充逻辑
- 注意懒标记的下传和信息的正确合并
- 处理边界情况和效率优化
- 1
信息
- ID
- 4362
- 时间
- 1000ms
- 内存
- 256MiB
- 难度
- 10
- 标签
- 递交数
- 2
- 已通过
- 1
- 上传者