线段树解决区间合并类问题

如果只维持一段区间连续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();
	}

}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值