倍增算法和ST表

前置知识

java 复制代码
public static void show1(int x, int m) {
		for (int p = m - 1, t = x; p >= 0; p--) {
			if (1 << p <= t) {
				t -= 1 << p;
				System.out.println(x + "的第" + p + "位是1");
			} else {
				System.out.println(x + "的第" + p + "位是0");
			}
		}
	}
java 复制代码
public static void show2(int x) {
		int power = 0;
		// 以下注释掉的写法不对,没有考虑溢出
//		while ((1 << power) <= x) {
//			power++;
//		}
//		power--;
		// 防止溢出的写法
		while ((1 << power) <= (x >> 1)) {
			power++;
		}
		System.out.println("<=" + x + "最大的2的幂,是2的" + power + "次方");
	}

1.建立一张表,横向表示位置,纵向表示从某个点跳2^i次方步最远到达的位置。

第一列跳一步最远到达的点题目给出,后面的每一列都可以由状态转移方程推出

2.求从a-b需要的最少步数,例如从17-77:

首先从17-77最多要走60步,将60转换为二进制需要0-5位,从5开始不停的枚举最远到达的位置但要小于77,最后得到的二进制数+1就是最少需要的步数

题目1

P4155 [SCOI2015] 国旗计划 - 洛谷 | 计算机科学教育新生态 (luogu.com.cn)

建立st表:每个线段走对应的步数能到达的最远的线段

java 复制代码
package class117;

// 国旗计划
// 给定点的数量m,点的编号1~m,所有点围成一个环
// i号点一定顺时针到达i+1号点,最终m号点顺指针回到1号点
// 给定n条线段,每条线段(a, b),表示线段从点a顺时针到点b
// 输入数据保证所有线段可以把整个环覆盖
// 输入数据保证每条线段不会完全在另一条线段的内部
// 也就是线段之间可能有重合但一定互不包含
// 返回一个长度为n的结果数组ans,ans[x]表示一定选x号线段的情况下
// 至少选几条线段能覆盖整个环
// 测试链接 : https://www.luogu.com.cn/problem/P4155
// 请同学们务必参考如下代码中关于输入、输出的处理
// 这是输入输出处理效率很高的写法
// 提交以下的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;
import java.util.Arrays;

public class Code01_FlagPlan {

	public static int MAXN = 200001;

	public static int LIMIT = 18;

	public static int power;

	// 每条线段3个信息 : 线段编号、线段左边界、线段右边界
	public static int[][] line = new int[MAXN << 1][3];

	// stjump[i][p] : 从i号线段出发,跳的次数是2的p次方,能到达的最右线段的编号
	public static int[][] stjump = new int[MAXN << 1][LIMIT];

	public static int[] ans = new int[MAXN];

	public static int n, m;

	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();
		n = (int) in.nval;
		in.nextToken();
		m = (int) in.nval;
		for (int i = 1; i <= n; i++) {
			line[i][0] = i;
			in.nextToken();
			line[i][1] = (int) in.nval;
			in.nextToken();
			line[i][2] = (int) in.nval;
		}
		compute();
		out.print(ans[1]);
		for (int i = 2; i <= n; i++) {
			out.print(" " + ans[i]);
		}
		out.println();
		out.flush();
		out.close();
		br.close();
	}

	public static void compute() {
		power = log2(n);
		build();
		for (int i = 1; i <= n; i++) {
			ans[line[i][0]] = jump(i);
		}
	}

	// <=n最接近的2的幂,是2的几次方
	public static int log2(int n) {
		int ans = 0;
		while ((1 << ans) <= (n >> 1)) {
			ans++;
		}
		return ans;
	}

	public static void build() {
		for (int i = 1; i <= n; i++) {
			if (line[i][1] > line[i][2]) {
				line[i][2] += m;
			}
		}
		Arrays.sort(line, 1, n + 1, (a, b) -> a[1] - b[1]);
		for (int i = 1; i <= n; i++) {
			line[i + n][0] = line[i][0];
			line[i + n][1] = line[i][1] + m;
			line[i + n][2] = line[i][2] + m;
		}
		int e = n << 1;
		for (int i = 1, arrive = 1; i <= e; i++) {
			while (arrive + 1 <= e && line[arrive + 1][1] <= line[i][2]) {
				arrive++;
			}
			stjump[i][0] = arrive;
		}
		for (int p = 1; p <= power; p++) {
			for (int i = 1; i <= e; i++) {
				stjump[i][p] = stjump[stjump[i][p - 1]][p - 1];
			}
		}
	}

	public static int jump(int i) {
		int aim = line[i][1] + m, cur = i, next, ans = 0;
		for (int p = power; p >= 0; p--) {
			next = stjump[cur][p];
			if (next != 0 && line[next][2] < aim) {
				ans += 1 << p;
				cur = next;
			}
		}
		return ans + 1 + 1;
	}

}

题目2

P2880 [USACO07JAN] Balanced Lineup G - 洛谷

1.建立关于最大值或最小值的st表,表示从i位置起(包含i位置)向右2^p范围上的最大值是多少。转移方程如下

2.查询从 i 到 j 的最大值:以从17-37举例:

17-37长度为21,最接近21的2的幂是4,先求出17---32(17+16-1)的最大值,然后求22(37-16+1)---37的最大值,两者取最大就是最大值;最小值同理

java 复制代码
// ST表查询最大值和最小值
// 给定一个长度为n的数组arr,一共有m次查询
// 每次查询arr[l~r]上的最大值和最小值
// 每次查询只需要打印最大值-最小值的结果
// 测试链接 : https://www.luogu.com.cn/problem/P2880
// 请同学们务必参考如下代码中关于输入、输出的处理
// 这是输入输出处理效率很高的写法
// 提交以下的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 Code02_SparseTableMaximumMinimum {

	public static int MAXN = 50001;

	// 2的15次方是<=50001且最接近的
	// 所以次方可能是0~15
	// 于是准备16长度够用了
	public static int LIMIT = 16;

	public static int[] arr = new int[MAXN];

	// log2[i] : 查询<=i情况下,最大的2的幂,是2的几次方
	public static int[] log2 = new int[MAXN];

	public static int[][] stmax = new int[MAXN][LIMIT];

	public static int[][] stmin = new int[MAXN][LIMIT];

	public static void build(int n) {
		log2[0] = -1;
		for (int i = 1; i <= n; i++) {
			log2[i] = log2[i >> 1] + 1;
			stmax[i][0] = arr[i];
			stmin[i][0] = arr[i];
		}
		for (int p = 1; p <= log2[n]; p++) {
			for (int i = 1; i + (1 << p) - 1 <= n; i++) {
				stmax[i][p] = Math.max(stmax[i][p - 1], stmax[i + (1 << (p - 1))][p - 1]);
				stmin[i][p] = Math.min(stmin[i][p - 1], stmin[i + (1 << (p - 1))][p - 1]);
			}
		}
	}

	public static int query(int l, int r) {
		int p = log2[r - l + 1];
		int a = Math.max(stmax[l][p], stmax[r - (1 << p) + 1][p]);
		int b = Math.min(stmin[l][p], stmin[r - (1 << p) + 1][p]);
		return a - b;
	}

	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(n);
		for (int i = 1, l, r; i <= m; i++) {
			in.nextToken();
			l = (int) in.nval;
			in.nextToken();
			r = (int) in.nval;
			out.println(query(l, r));
		}
		out.flush();
		out.close();
		br.close();
	}

}

适用范围

题目3

P1890 gcd 区间 - 洛谷

java 复制代码
// ST表查询最大公约数
// 给定一个长度为n的数组arr,一共有m次查询
// 每次查询arr[l~r]上所有数的最大公约数
// 测试链接 : https://www.luogu.com.cn/problem/P1890
// 请同学们务必参考如下代码中关于输入、输出的处理
// 这是输入输出处理效率很高的写法
// 提交以下的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 Code03_SparseTableGCD {

	public static int MAXN = 1001;

	public static int LIMIT = 10;

	public static int[] arr = new int[MAXN];

	public static int[] log2 = new int[MAXN];

	public static int[][] stgcd = new int[MAXN][LIMIT];

	public static void build(int n) {
		log2[0] = -1;
		for (int i = 1; i <= n; i++) {
			log2[i] = log2[i >> 1] + 1;
			stgcd[i][0] = arr[i];
		}
		for (int p = 1; p <= log2[n]; p++) {
			for (int i = 1; i + (1 << p) - 1 <= n; i++) {
				stgcd[i][p] = gcd(stgcd[i][p - 1], stgcd[i + (1 << (p - 1))][p - 1]);
			}
		}
	}

	// 算法讲解041 - 辗转相除法求最大公约数
	public static int gcd(int a, int b) {
		return b == 0 ? a : gcd(b, a % b);
	}

	public static int query(int l, int r) {
		int p = log2[r - l + 1];
		return gcd(stgcd[l][p], stgcd[r - (1 << p) + 1][p]);
	}

	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(n);
		for (int i = 1, l, r; i <= m; i++) {
			in.nextToken();
			l = (int) in.nval;
			in.nextToken();
			r = (int) in.nval;
			out.println(query(l, r));
		}
		out.flush();
		out.close();
		br.close();
	}

}

题目4

UVA11235 Frequent values - 洛谷

建立几个预处理机构:根据数组是有序的,把相同的数字分到一个桶里,统计每个桶里有多少个数字,每个桶的下标范围,每个下标对应的桶的编号。例如,求下表6~26出现次数最多的数字的个数,首先找到6的桶的编号2,2号桶的下标范围右边界到7,所以有2个2;26的桶是8,8号桶的左边界是25,所以有2个6;然后再求8-24对应的桶3-7中词频的最大值,用st表处理

java 复制代码
// 出现次数最多的数有几个
// 给定一个长度为n的数组arr,该数组一定是有序的
// 一共有m次查询,每次查询arr[l~r]上出现次数最多的数有几个
// 对数器验证

import java.util.Arrays;
import java.util.HashMap;

public class Code04_FrequentValues1 {

	public static int MAXN = 100001;

	public static int LIMIT = 17;

	public static int[] arr = new int[MAXN];

	public static int[] log2 = new int[MAXN];

	public static int[] bucket = new int[MAXN];

	public static int[] left = new int[MAXN];

	public static int[] right = new int[MAXN];

	public static int[][] stmax = new int[MAXN][LIMIT];

	public static void build(int n) {
		// 题目给定的数值范围-100000 ~ +100000
		// 把arr[0]设置成不会到达的数字即可
		arr[0] = -23333333;
		int cnt = 0;
		for (int i = 1; i <= n; i++) {
			if (arr[i - 1] != arr[i]) {
				right[cnt] = i - 1;
				left[++cnt] = i;
			}
			bucket[i] = cnt;
		}
		right[cnt] = n;
		log2[0] = -1;
		for (int i = 1; i <= cnt; i++) {
			log2[i] = log2[i >> 1] + 1;
			stmax[i][0] = right[i] - left[i] + 1;
		}
		for (int p = 1; p <= log2[cnt]; p++) {
			for (int i = 1; i + (1 << p) - 1 <= cnt; i++) {
				stmax[i][p] = Math.max(stmax[i][p - 1], stmax[i + (1 << (p - 1))][p - 1]);
			}
		}
	}

	public static int query(int l, int r) {
		if (l > r) {
			int tmp = l;
			l = r;
			r = tmp;
		}
		int lbucket = bucket[l];
		int rbucket = bucket[r];
		if (lbucket == rbucket) {
			return r - l + 1;
		}
		// a : 最左侧桶在此时l~r范围上的数字有几个
		// b : 最右侧桶在此时l~r范围上的数字有几个
		int a = right[lbucket] - l + 1, b = r - left[rbucket] + 1, c = 0;
		if (lbucket + 1 < rbucket) {
			int from = lbucket + 1, to = rbucket - 1, p = log2[to - from + 1];
			c = Math.max(stmax[from][p], stmax[to - (1 << p) + 1][p]);
		}
		return Math.max(Math.max(a, b), c);
	}

	// 对数器
	// 为了验证
	public static void main(String[] args) {
		System.out.println("测试开始");
		int n = 10000;
		int v = 100;
		int m = 5000;
		randomArray(n, v);
		build(n);
		for (int i = 1, l, r; i <= m; i++) {
			l = (int) (Math.random() * n) + 1;
			r = (int) (Math.random() * n) + 1;
			if (query(l, r) != checkQuery(l, r)) {
				System.out.println("出错了!");
			}
		}
		System.out.println("测试结束");
	}

	// 得到随机数组
	// 为了验证
	public static void randomArray(int n, int v) {
		for (int i = 1; i <= n; i++) {
			arr[i] = (int) (Math.random() * 2 * v) - v;
		}
		Arrays.sort(arr, 1, n + 1);
	}

	// 暴力方法
	// 直接遍历统计词频
	// 为了验证
	public static int checkQuery(int l, int r) {
		if (l > r) {
			int tmp = l;
			l = r;
			r = tmp;
		}
		HashMap<Integer, Integer> map = new HashMap<>();
		for (int i = l; i <= r; i++) {
			map.put(arr[i], map.getOrDefault(arr[i], 0) + 1);
		}
		int ans = 0;
		for (int v : map.values()) {
			ans = Math.max(ans, v);
		}
		return ans;
	}

}
相关推荐
知识领航员2 小时前
蘑兔AI音乐深度实测:功能拆解、实测表现与适用场景
java·c语言·c++·人工智能·python·算法·github
薛定e的猫咪2 小时前
因果推理研究方向综述笔记
人工智能·笔记·深度学习·算法
如何原谅奋力过但无声3 小时前
【灵神高频面试题合集06-08】反转链表、快慢指针(环形链表/重排链表)、前后指针(删除链表/链表去重)
数据结构·python·算法·leetcode·链表
平行侠3 小时前
037插入排序 - 整理扑克牌的算法
数据结构·算法
ECT-OS-JiuHuaShan3 小时前
彻底定理化:从量子纠缠到量子代谢
数据库·人工智能·学习·算法·生活·量子计算
爱喝雪碧的可乐4 小时前
2026 腾讯广告算法大赛优秀方案启示:行为条件化多模态自回归生成推荐摘要
算法·数据挖掘·回归·推荐系统·推荐算法
碧海银沙音频科技研究院4 小时前
音箱在加入 NN AEC(神经网络声学回声消除) 后出现反复重启问题解决
人工智能·深度学习·算法
叼烟扛炮5 小时前
C++ 知识点18 内部类
开发语言·c++·算法·内部类