如果只维持一段区间连续1的最长字串长度是无法被线段树维护的:所以可以增加信息来共同维护
1.维护三个信息:连续1的最长字串长度、连续1的最长前缀长度、连续1的最长后缀长度
2.如果一段区域连续1的长度小于区域的总长度:
3.如果一段区域连续1的字串长度等于区域的总长度:
P2572 [SCOI2010] 序列操作 - 洛谷 | 计算机科学教育新生态 (luogu.com.cn)
1.为了执行翻转操作,不仅需要记录1的连续最长字串长度、前缀长度、后缀长度,还需要记录0的连续最长字串长度、前缀长度、后缀长度
2.注意update和reverse操作的优先级:update会覆盖reverse
// 序列操作
// 给定一个长度为n的数组arr,内部只有01两种值,下标从0开始
// 对于这个序列有五种变换操作和询问操作
// 操作 0 l r : 把l~r范围上所有数字全改成0
// 操作 1 l r : 把l~r范围上所有数字全改成1
// 操作 2 l r : 把l~r范围上所有数字全取反
// 操作 3 l r : 询问l~r范围上有多少个1
// 操作 4 l r : 询问l~r范围上连续1的最长子串长度
// 测试链接 : https://www.luogu.com.cn/problem/P2572
// 请同学们务必参考如下代码中关于输入、输出的处理
// 这是输入输出处理效率很高的写法
// 提交以下的code,提交时请把类名改成"Main",可以直接通过
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.OutputStreamWriter;
import java.io.PrintWriter;
import java.io.StreamTokenizer;
public class Main{
public static int MAXN = 100001;
// 原始数组
public static int[] arr = new int[MAXN];
// 累加和用来统计1的数量
public static int[] sum = new int[MAXN << 2];
// 连续0的最长子串长度
public static int[] len0 = new int[MAXN << 2];
// 连续0的最长前缀长度
public static int[] pre0 = new int[MAXN << 2];
// 连续0的最长后缀长度
public static int[] suf0 = new int[MAXN << 2];
// 连续1的最长子串长度
public static int[] len1 = new int[MAXN << 2];
// 连续1的最长前缀长度
public static int[] pre1 = new int[MAXN << 2];
// 连续1的最长后缀长度
public static int[] suf1 = new int[MAXN << 2];
// 懒更新信息,范围上所有数字被重置成了什么
public static int[] change = new int[MAXN << 2];
// 懒更新信息,范围上有没有重置任务
public static boolean[] update = new boolean[MAXN << 2];
// 懒更新信息,范围上有没有翻转任务
public static boolean[] reverse = new boolean[MAXN << 2];
public static void up(int i, int ln, int rn) {
int l = i << 1;
int r = i << 1 | 1;
sum[i] = sum[l] + sum[r];
len0[i] = Math.max(Math.max(len0[l], len0[r]), suf0[l] + pre0[r]);
pre0[i] = len0[l] < ln ? pre0[l] : (pre0[l] + pre0[r]);
suf0[i] = len0[r] < rn ? suf0[r] : (suf0[l] + suf0[r]);
len1[i] = Math.max(Math.max(len1[l], len1[r]), suf1[l] + pre1[r]);
pre1[i] = len1[l] < ln ? pre1[l] : (pre1[l] + pre1[r]);
suf1[i] = len1[r] < rn ? suf1[r] : (suf1[l] + suf1[r]);
}
public static void down(int i, int ln, int rn) {
if (update[i]) {
updateLazy(i << 1, change[i], ln);
updateLazy(i << 1 | 1, change[i], rn);
update[i] = false;
}
if (reverse[i]) {
reverseLazy(i << 1, ln);
reverseLazy(i << 1 | 1, rn);
reverse[i] = false;
}
}
public static void updateLazy(int i, int v, int n) {
sum[i] = v * n;
len0[i] = pre0[i] = suf0[i] = v == 0 ? n : 0;
len1[i] = pre1[i] = suf1[i] = v == 1 ? n : 0;
change[i] = v;
update[i] = true;
reverse[i] = false;
}
public static void reverseLazy(int i, int n) {
sum[i] = n - sum[i];
int tmp;
tmp = len0[i]; len0[i] = len1[i]; len1[i] = tmp;
tmp = pre0[i]; pre0[i] = pre1[i]; pre1[i] = tmp;
tmp = suf0[i]; suf0[i] = suf1[i]; suf1[i] = tmp;
reverse[i] = !reverse[i];
}
public static void build(int l, int r, int i) {
if (l == r) {
sum[i] = arr[l];
len0[i] = pre0[i] = suf0[i] = arr[l] == 0 ? 1 : 0;
len1[i] = pre1[i] = suf1[i] = arr[l] == 1 ? 1 : 0;
} else {
int mid = (l + r) >> 1;
build(l, mid, i << 1);
build(mid + 1, r, i << 1 | 1);
up(i, mid - l + 1, r - mid);
}
update[i] = false;
reverse[i] = false;
}
public static void update(int jobl, int jobr, int jobv, int l, int r, int i) {
if (jobl <= l && r <= jobr) {
updateLazy(i, jobv, r - l + 1);
} else {
int mid = (l + r) >> 1;
down(i, mid - l + 1, r - mid);
if (jobl <= mid) {
update(jobl, jobr, jobv, l, mid, i << 1);
}
if (jobr > mid) {
update(jobl, jobr, jobv, mid + 1, r, i << 1 | 1);
}
up(i, mid - l + 1, r - mid);
}
}
public static void reverse(int jobl, int jobr, int l, int r, int i) {
if (jobl <= l && r <= jobr) {
reverseLazy(i, r - l + 1);
} else {
int mid = (l + r) >> 1;
down(i, mid - l + 1, r - mid);
if (jobl <= mid) {
reverse(jobl, jobr, l, mid, i << 1);
}
if (jobr > mid) {
reverse(jobl, jobr, mid + 1, r, i << 1 | 1);
}
up(i, mid - l + 1, r - mid);
}
}
// 线段树范围l~r上,被jobl~jobr影响的区域里,返回1的数量
public static int querySum(int jobl, int jobr, int l, int r, int i) {
if (jobl <= l && r <= jobr) {
return sum[i];
}
int mid = (l + r) >> 1;
down(i, mid - l + 1, r - mid);
int ans = 0;
if (jobl <= mid) {
ans += querySum(jobl, jobr, l, mid, i << 1);
}
if (jobr > mid) {
ans += querySum(jobl, jobr, mid + 1, r, i << 1 | 1);
}
return ans;
}
// 返回一个长度为3的数组ans,代表结果,具体含义如下:
// ans[0] : 线段树范围l~r上,被jobl~jobr影响的区域里,连续1的最长子串长度
// ans[1] : 线段树范围l~r上,被jobl~jobr影响的区域里,连续1的最长前缀长度
// ans[2] : 线段树范围l~r上,被jobl~jobr影响的区域里,连续1的最长后缀长度
public static int[] queryLongest(int jobl, int jobr, int l, int r, int i) {
if (jobl <= l && r <= jobr) {
return new int[] { len1[i], pre1[i], suf1[i] };
} else {
int mid = (l + r) >> 1;
int ln = mid - l + 1;
int rn = r - mid;
down(i, ln, rn);
if (jobr <= mid) {
return queryLongest(jobl, jobr, l, mid, i << 1);
}
if (jobl > mid) {
return queryLongest(jobl, jobr, mid + 1, r, i << 1 | 1);
}
int[] l3 = queryLongest(jobl, jobr, l, mid, i << 1);
int[] r3 = queryLongest(jobl, jobr, mid + 1, r, i << 1 | 1);
int llen = l3[0], lpre = l3[1], lsuf = l3[2];
int rlen = r3[0], rpre = r3[1], rsuf = r3[2];
int len = Math.max(Math.max(llen, rlen), lsuf + rpre);
// 任务实际影响了左侧范围的几个点 -> mid - Math.max(jobl, l) + 1
int pre = llen < mid - Math.max(jobl, l) + 1 ? lpre : (lpre + rpre);
// 任务实际影响了右侧范围的几个点 -> Math.min(r, jobr) - mid
int suf = rlen < Math.min(r, jobr) - mid ? rsuf : (lsuf + rsuf);
return new int[] { len, pre, suf };
}
}
public static void main(String[] args) throws IOException {
BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
StreamTokenizer in = new StreamTokenizer(br);
PrintWriter out = new PrintWriter(new OutputStreamWriter(System.out));
in.nextToken();
int n = (int) in.nval;
in.nextToken();
int m = (int) in.nval;
for (int i = 1; i <= n; i++) {
in.nextToken();
arr[i] = (int) in.nval;
}
build(1, n, 1);
for (int i = 1, op, jobl, jobr; i <= m; i++) {
in.nextToken();
op = (int) in.nval;
in.nextToken();
jobl = (int) in.nval + 1; // 注意题目给的下标从0开始,线段树下标从1开始
in.nextToken();
jobr = (int) in.nval + 1; // 注意题目给的下标从0开始,线段树下标从1开始
if (op == 0) {
update(jobl, jobr, 0, 1, n, 1);
} else if (op == 1) {
update(jobl, jobr, 1, 1, n, 1);
} else if (op == 2) {
reverse(jobl, jobr, 1, n, 1);
} else if (op == 3) {
out.println(querySum(jobl, jobr, 1, n, 1));
} else {
out.println(queryLongest(jobl, jobr, 1, n, 1)[0]);
}
}
out.flush();
out.close();
br.close();
}
}
P6492 [COCI2010-2011#6] STEP - 洛谷 | 计算机科学教育新生态 (luogu.com.cn)
1.单点修改,不需要down和lazy函数
2.根据边界字符判断两段区域是否可以组成新的交替字符串
// 最长LR交替子串
// 给定一个长度为n的字符串,一开始字符串中全是'L'字符
// 有q次修改,每次指定一个位置i
// 如果i位置是'L'字符那么改成'R'字符
// 如果i位置是'R'字符那么改成'L'字符
// 如果一个子串是两种字符不停交替出现的样子,也就是LRLR... 或者RLRL...
// 那么说这个子串是有效子串
// 每次修改后,都打印当前整个字符串中最长交替子串的长度
// 测试链接 : https://www.luogu.com.cn/problem/P6492
// 请同学们务必参考如下代码中关于输入、输出的处理
// 这是输入输出处理效率很高的写法
// 提交以下的code,提交时请把类名改成"Main",可以直接通过
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.OutputStreamWriter;
import java.io.PrintWriter;
import java.io.StreamTokenizer;
public class Main {
public static int MAXN = 200001;
// 原始数组
public static int[] arr = new int[MAXN];
// 交替最长子串长度
public static int[] len = new int[MAXN << 2];
// 交替最长前缀长度
public static int[] pre = new int[MAXN << 2];
// 交替最长后缀长度
public static int[] suf = new int[MAXN << 2];
public static void up(int l, int r, int i) {
len[i] = Math.max(len[i << 1], len[i << 1 | 1]);
pre[i] = pre[i << 1];
suf[i] = suf[i << 1 | 1];
int mid = (l + r) >> 1;
int ln = mid - l + 1;
int rn = r - mid;
if (arr[mid] != arr[mid + 1]) {
len[i] = Math.max(len[i], suf[i << 1] + pre[i << 1 | 1]);
if (len[i << 1] == ln) {
pre[i] = ln + pre[i << 1 | 1];
}
if (len[i << 1 | 1] == rn) {
suf[i] = rn + suf[i << 1];
}
}
}
public static void build(int l, int r, int i) {
if (l == r) {
len[i] = 1;
pre[i] = 1;
suf[i] = 1;
} else {
int mid = (l + r) >> 1;
build(l, mid, i << 1);
build(mid + 1, r, i << 1 | 1);
up(l, r, i);
}
}
public static void reverse(int jobi, int l, int r, int i) {
if (l == r) {
arr[jobi] ^= 1;
} else {
int mid = (l + r) >> 1;
if (jobi <= mid) {
reverse(jobi, l, mid, i << 1);
} else {
reverse(jobi, mid + 1, r, i << 1 | 1);
}
up(l, r, i);
}
}
public static void main(String[] args) throws IOException {
BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
StreamTokenizer in = new StreamTokenizer(br);
PrintWriter out = new PrintWriter(new OutputStreamWriter(System.out));
in.nextToken();
int n = (int) in.nval;
in.nextToken();
int q = (int) in.nval;
build(1, n, 1);
for (int i = 1, index; i <= q; i++) {
in.nextToken();
index = (int) in.nval;
reverse(index, 1, n, 1);
out.println(len[1]);
}
out.flush();
out.close();
br.close();
}
}
P1503 鬼子进村 - 洛谷 | 计算机科学教育新生态 (luogu.com.cn)
1.此题由于查询操作的特殊性不需要记录区域的最长连续字串
2.为了执行恢复操作,可以用栈来记录摧毁过的村庄
// 地道相连的房子
// 有n个房子排成一排,编号1~n,一开始每相邻的两个房子之间都有地道
// 实现如下三个操作
// 操作 D x : 把x号房子摧毁,该房子附近的地道也一并摧毁
// 操作 R : 恢复上次摧毁的房子,该房子附近的地道一并恢复
// 操作 Q x : 查询x号房子能到达的房子数量,包括x号房子自身
// 测试链接 : https://www.luogu.com.cn/problem/P1503
// 请同学们务必参考如下代码中关于输入、输出的处理
// 这是输入输出处理效率很高的写法
// 提交以下的code,提交时请把类名改成"Main",可以直接通过
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.OutputStreamWriter;
import java.io.PrintWriter;
import java.io.StreamTokenizer;
public class Main {
public static int MAXN = 50001;
// 连续1的最长前缀长度
public static int[] pre = new int[MAXN << 2];
// 连续1的最长后缀长度
public static int[] suf = new int[MAXN << 2];
// 摧毁的房屋编号入栈,以便执行恢复操作
public static int[] stack = new int[MAXN];
public static void up(int l, int r, int i) {
pre[i] = pre[i << 1];
suf[i] = suf[i << 1 | 1];
int mid = (l + r) >> 1;
if (pre[i << 1] == mid - l + 1) {
pre[i] += pre[i << 1 | 1];
}
if (suf[i << 1 | 1] == r - mid) {
suf[i] += suf[i << 1];
}
}
public static void build(int l, int r, int i) {
if (l == r) {
pre[i] = suf[i] = 1;
} else {
int mid = (l + r) >> 1;
build(l, mid, i << 1);
build(mid + 1, r, i << 1 | 1);
up(l, r, i);
}
}
public static void update(int jobi, int jobv, int l, int r, int i) {
if (l == r) {
pre[i] = suf[i] = jobv;
} else {
int mid = (l + r) >> 1;
if (jobi <= mid) {
update(jobi, jobv, l, mid, i << 1);
} else {
update(jobi, jobv, mid + 1, r, i << 1 | 1);
}
up(l, r, i);
}
}
// 已知jobi在l...r范围上
// 返回jobi往两侧扩展出的最大长度
// 递归需要遵循的潜台词 : 从jobi往两侧扩展,一定无法扩展到l...r范围之外!
public static int query(int jobi, int l, int r, int i) {
if (l == r) {
return pre[i];
} else {
int mid = (l + r) >> 1;
if (jobi <= mid) {
if (jobi > mid - suf[i << 1]) {
return suf[i << 1] + pre[i << 1 | 1];
} else {
return query(jobi, l, mid, i << 1);
}
} else {
if (mid + pre[i << 1 | 1] >= jobi) {
return suf[i << 1] + pre[i << 1 | 1];
} else {
return query(jobi, mid + 1, r, i << 1 | 1);
}
}
}
}
public static void main(String[] args) throws IOException {
BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
StreamTokenizer in = new StreamTokenizer(br);
PrintWriter out = new PrintWriter(new OutputStreamWriter(System.out));
while (in.nextToken() != StreamTokenizer.TT_EOF) {
int n = (int) in.nval;
in.nextToken();
int m = (int) in.nval;
build(1, n, 1);
String op;
int stackSize = 0;
for (int i = 1, x; i <= m; i++) {
in.nextToken();
op = in.sval;
if (op.equals("D")) {
in.nextToken();
x = (int) in.nval;
update(x, 0, 1, n, 1);
stack[stackSize++] = x;
} else if (op.equals("R")) {
update(stack[--stackSize], 1, 1, n, 1);
} else {
in.nextToken();
x = (int) in.nval;
out.println(query(x, 1, n, 1));
}
}
}
out.flush();
out.close();
br.close();
}
}
P2894 [USACO08FEB] Hotel G - 洛谷 | 计算机科学教育新生态 (luogu.com.cn)
此题查询操作较为特殊:查询时,要求返回字典序较小的编号,所以要从左区域到中间区域到右区域查询,只要有满足条件的就返回
// 旅馆
// 一共有n个房间,编号1~n,一开始都是空房
// 实现如下两种操作,会一共调用m次
// 操作 1 x : 找到至少有连续x个空房间的区域,返回最左编号
// 如果有多个满足条件的区域,返回其中最左区域的最左编号
// 如果找不到打印0,并且不办理入住
// 如果找到了打印最左编号,并且从最左编号开始办理x个人的入住
// 操作 2 x y : 从x号房间开始往下数y个房间,一律清空
// 操作1有打印操作,操作2没有
// 1 <= n 、m <= 50000
// 测试链接 : https://www.luogu.com.cn/problem/P2894
// 请同学们务必参考如下代码中关于输入、输出的处理
// 这是输入输出处理效率很高的写法
// 提交以下的code,提交时请把类名改成"Main",可以直接通过
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.OutputStreamWriter;
import java.io.PrintWriter;
import java.io.StreamTokenizer;
public class Main{
public static int MAXN = 50001;
// 连续空房最长子串长度
public static int[] len = new int[MAXN << 2];
// 连续空房最长前缀长度
public static int[] pre = new int[MAXN << 2];
// 连续空房最长后缀长度
public static int[] suf = new int[MAXN << 2];
// 懒更新信息,范围上所有数字被重置成了什么
public static int[] change = new int[MAXN << 2];
// 懒更新信息,范围上有没有重置任务
public static boolean[] update = new boolean[MAXN << 2];
public static void up(int i, int ln, int rn) {
int l = i << 1;
int r = i << 1 | 1;
len[i] = Math.max(Math.max(len[l], len[r]), suf[l] + pre[r]);
pre[i] = len[l] < ln ? pre[l] : (pre[l] + pre[r]);
suf[i] = len[r] < rn ? suf[r] : (suf[l] + suf[r]);
}
public static void down(int i, int ln, int rn) {
if (update[i]) {
lazy(i << 1, change[i], ln);
lazy(i << 1 | 1, change[i], rn);
update[i] = false;
}
}
public static void lazy(int i, int v, int n) {
len[i] = pre[i] = suf[i] = v == 0 ? n : 0;
change[i] = v;
update[i] = true;
}
public static void build(int l, int r, int i) {
if (l == r) {
len[i] = pre[i] = suf[i] = 1;
} else {
int mid = (l + r) >> 1;
build(l, mid, i << 1);
build(mid + 1, r, i << 1 | 1);
up(i, mid - l + 1, r - mid);
}
update[i] = false;
}
public static void update(int jobl, int jobr, int jobv, int l, int r, int i) {
if (jobl <= l && r <= jobr) {
lazy(i, jobv, r - l + 1);
} else {
int mid = (l + r) >> 1;
down(i, mid - l + 1, r - mid);
if (jobl <= mid) {
update(jobl, jobr, jobv, l, mid, i << 1);
}
if (jobr > mid) {
update(jobl, jobr, jobv, mid + 1, r, i << 1 | 1);
}
up(i, mid - l + 1, r - mid);
}
}
// 在l..r范围上,在满足空房长度>=x的情况下,返回尽量靠左的开头位置
// 递归需要遵循的潜台词 : l..r范围上一定存在连续空房长度>=x的区域
public static int queryLeft(int x, int l, int r, int i) {
if (l == r) {
return l;
} else {
int mid = (l + r) >> 1;
down(i, mid - l + 1, r - mid);
// 最先查左边
if (len[i << 1] >= x) {
return queryLeft(x, l, mid, i << 1);
}
// 然后查中间向两边扩展的可能区域
if (suf[i << 1] + pre[i << 1 | 1] >= x) {
return mid - suf[i << 1] + 1;
}
// 前面都没有再最后查右边
return queryLeft(x, mid + 1, r, i << 1 | 1);
}
}
public static void main(String[] args) throws IOException {
BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
StreamTokenizer in = new StreamTokenizer(br);
PrintWriter out = new PrintWriter(new OutputStreamWriter(System.out));
in.nextToken();
int n = (int) in.nval;
build(1, n, 1);
in.nextToken();
int m = (int) in.nval;
for (int i = 1, op, x, y, left; i <= m; i++) {
in.nextToken();
op = (int) in.nval;
if (op == 1) {
in.nextToken();
x = (int) in.nval;
if (len[1] < x) {
left = 0;
} else {
left = queryLeft(x, 1, n, 1);
update(left, left + x - 1, 1, 1, n, 1);
}
out.println(left);
} else {
in.nextToken();
x = (int) in.nval;
in.nextToken();
y = (int) in.nval;
update(x, Math.min(x + y - 1, n), 0, 1, n, 1);
}
}
out.flush();
out.close();
br.close();
}
}