倍增法
倍增法与二分法是“相反”的算法,二分法是每次缩小一半,从而以
倍增就是“成倍增长”,很多时候倍增的实现利用的是二进制本身的倍增特性。把一个数
二进制划分反映了一种快速增长的特性,第
倍增法的局限性是需要提前计算出第
例题:P4155 [SCOI2015] 国旗计划
边境上有
个边防站围成一圈,顺时针编号为 。有 名战士,每名战士常驻两个站 和 ,能在两个站之间移动。局长有一个“国旗计划”,让边防战士举着国旗环绕一圈。局长想知道至少需要多少战士才能完成“国旗计划”,并且他想知道在某个战士必须参加的情况下,至少需要多少名边防战士。
数据范围:。
题目的要求很清晰:计算能覆盖整个圆圈的最少区间(战士)。
题目给定的所有区间互相不包含,那么按区间的左端点排序后,区间的右端点也是也是单调递增的。这种情况下能用贪心法选择区间。
定义
预计算出从所有的区间出发的
跳的时候先用大数再用小数。以从
- 从
跳 步,到达 后的第 个区间 ; - 从
跳 步,到达 后的第 个区间 ; - 从
跳 步到达 ; - 从
跳 步到达终点 。
时间复杂度是多少?查询一次,用倍增法从
剩下的问题是如何快速预计算出
。从 起跳,先跳 步到区间 ; 。再从 跳 步。
一共跳了
特别地,
以上所有计算,包括预计算
#include <cstdio> #include <algorithm> using namespace std; typedef long long LL; const int MAXN = 4e5 + 5; struct Interval { LL c, d; // c和d为战士的左右区间 int id; // id为战士的编号 bool operator<(const Interval& other) const { return c < other.c; } }; Interval a[MAXN]; int go[MAXN][20], n, m, ans[MAXN]; void init() { // 贪心+预计算倍增 int nxt = 1; for (int i = 1; i <= n; i++) { // 用贪心求每个区间的下一个最优区间 // 最优区间是有交集且右端点最大的区间 while (nxt <= n && a[nxt].c <= a[i].d) nxt++; go[i][0] = nxt - 1; // 区间i的下一个区间 } for (int i = 1; (1 << i) < n / 2; i++) // 倍增:i=1,2,4,8,...,共logn次 for (int s = 1; s <= n; s++) // 每个区间后第2的i次方个区间 go[s][i] = go[go[s][i - 1]][i - 1]; } int calc(int x) { // 从第x个战士出发 int ret = 1, dest = a[x].c + m; for (int i = 19; i >= 0; i--) { // 从最大的i开始找 int nxt = go[x][i]; if (nxt && a[nxt].d < dest) { x = nxt; // 跳到新位置 ret += 1 << i; // 累加跳过的区间个数 } } return ret + 1; } int main() { scanf("%d%d", &n, &m); for (int i = 1; i <= n; i++) { scanf("%lld%lld", &a[i].c, &a[i].d); a[i].id = i; // 记录战士的顺序 if (a[i].c > a[i].d) a[i].d += m; // 把环变成链 } sort(a + 1, a + n + 1); // 按左端点排序 for (int i = 1; i <= n; i++) { // 拆环加倍成一条链 a[n + i].c = a[i].c + m; a[n + i].d = a[i].d + m; } n *= 2; init(); for (int i = 1; i <= n / 2; i++) ans[a[i].id] = calc(i); // 逐个计算每个战士 for (int i = 1; i <= n / 2; i++) printf("%d%c", ans[i], i * 2 == n ? '\n' : ' '); return 0; }
习题:P8251 [NOI Online 2022 提高组] 丹钓战
解题思路
先按题意模拟整个序列的入栈出栈过程。当由于一个新元素想要入栈而导致某元素被弹出时,则说明如果被弹出元素是入栈序列中的第一个元素,则它的下一个“成功的”元素是这个想要入栈的元素。因此可以预处理每一个元素的下一个“成功的”元素是谁以及相应的倍增跳跃表。对于每一个查询序列,利用倍增表就可以快速计算整个序列中“成功的”元素的数量。
参考代码
#include <cstdio> #include <stack> using std::stack; const int N = 5e5 + 5; const int LOG = 19; int a[N], b[N], go[N][LOG]; int main() { int n, q; scanf("%d%d", &n, &q); for (int i = 1; i <= n; i++) scanf("%d", &a[i]); for (int i = 1; i <= n; i++) scanf("%d", &b[i]); stack<int> s; for (int i = 1; i <= n; i++) { while (!s.empty()) { int pre = s.top(); int ta = a[pre], tb = b[pre]; if (a[i] != ta && b[i] < tb) break; go[pre][0] = i; // 记录每个元素因为谁导致要弹出 s.pop(); } s.push(i); } for (int i = 1; i < LOG; i++) for (int j = 1; j <= n; j++) go[j][i] = go[go[j][i - 1]][i - 1]; // 预处理倍增表 while (q--) { int l, r; scanf("%d%d", &l, &r); int ans = 0; int cur = l; for (int i = LOG - 1; i >= 0; i--) if (go[cur][i] != 0 && go[cur][i] <= r) { // 向右跳但没有越过边界 // 跳了2的i次方步 ans += 1 << i; cur = go[cur][i]; } printf("%d\n", ans + 1); // 要算上起点本身 } return 0; }
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 分享一个免费、快速、无限量使用的满血 DeepSeek R1 模型,支持深度思考和联网搜索!
· 基于 Docker 搭建 FRP 内网穿透开源项目(很简单哒)
· ollama系列1:轻松3步本地部署deepseek,普通电脑可用
· 按钮权限的设计及实现
· 【杂谈】分布式事务——高大上的无用知识?