Page Top

基础算法——倍增 学习笔记

基础算法——倍增 学习笔记

概念

倍增法,顾名思义就是翻倍、成倍增长。

它能够使线性的处理转化为对数级的处理,大大地优化时间复杂度。

常常在递推中状态空间的第二维记录二的整数次幂的值,通过这些值拼凑出答案。

通过任意整数都可以表示为若干个二的次幂项的和(二进制),以此计算。

ST 表概述

见我 RMQ 笔记。

倍增 LCA 概述

见 LCA 笔记。

现以迁移到 Github:rainppr.github.io/blog。

例题

给定一个长度为 \(N\) 的序列 \(A\),对于询问的整数 \(T\),求出
最大的整数 \(k\),使得

\[\sum_{i=1}^kA_i\le T \]

找到在线算法。

首先,可以求出 \(A\) 的前缀和 \(S\),然后就可以简单的二分了。

但是,如果答案 \(k\) 非常小,二分的算法就会很不优,甚至不如顺序枚举。

考虑倍增算法(下面是蓝书上的),

  • \(p=1,k=0,r=0\)
  • 如果 \(r+S_{k+p}-S_k\le T\),就另 \(r\gets r+S_{k+p}-S_k\)\(k\gets k+p\)\(p\gets2p\)
  • 否则,另 \(p\gets p/2\)
  • 重复上一步,直至 \(p=0\),此时 \(k\) 即为答案。

这个思想与倍增 LCA 的思想不同,是先跳,如果能跳就增大下一次跳的,跳不了就减小。

天才 ACM

题目:https://www.acwing.com/problem/content/111/

考虑到,一个集合的校验值,一定是最大对最小,次大对次小。

随便举个例子,若 \(a<b<c<d\),则

\[(d-a)^2+(c-b)^2=a^2+b^2+c^2+d^2-2(ad+bc)\\ (b-a)^2+(d-c)^2=a^2+b^2+c^2+d^2-2(ab+cd) \]

上式减下式,

\[ab+cd-ad-bc=a(b-d)+c(d-b)=(c-a)(d-b) \]

乘积为正数,即上式大于下式,即贪心可行且正确。

回归问题,容易总结出来:

对于左端点,找到最右的点,使得校验值小于限制的值。

考虑到计算校验值是 \(\mathcal O(n\log n)\) 的,因此这里需要优化。

注意到和上面的题形式类似,可以倍增处理,

因为倍增的复杂度是 \(\mathcal O(\log n)\) 的,因此整体复杂度为,

\[\mathcal O(n\log^2n) \]

不太可过,但是注意到每次右端点增加的时候,可以类似归并排序的合并。

于是复杂度降为,

\[\mathcal O(n\log n) \]

但是细节很多,本人采用了闭区间的写法,

#include <bits/stdc++.h>

using namespace std;

#define endl '\n'

using ll = long long;
constexpr int N = 1e6 + 10;

int n, m;
ll t;
int a[N], b[N];
int q[N];

bool getchk(int l, int r, int ad) {
	int lt = r - ad + 1;
    for (int i = lt; i <= r; ++i) b[i] = a[i];
    sort(b + lt, b + r + 1);
    int tot = 0, u = l, v = lt;
	while (u < lt && v <= r) {
		if (b[u] < b[v]) q[tot++] = b[u++];
		else q[tot++] = b[v++];
	}
	while (u < lt) q[tot++] = b[u++];
	while (v <= r) q[tot++] = b[v++];
	ll chk = 0;
	for (int i = 0, j = tot - 1, k = 1; k <= m && i < j; ++i, --j, ++k)
	chk += 1ll * (q[j] - q[i]) * (q[j] - q[i]);
	return chk <= t;
}

int getpos(int l) {
	int p = 1, k = l - 1;
	while (p) {
		if (k + p <= n && getchk(l, k + p, p)) {
			k = k + p, p <<= 1;
			for (int i = l; i <= k + p; ++i) b[i] = q[i - l];
		} else p >>= 1;
	} return k;
}

void solev() {
	cin >> n >> m >> t;
	for (int i = 1; i <= n; ++i) cin >> a[i];
	int l = 1, ans = 0;
	while (l <= n) l = getpos(l) + 1, ++ans;
	cout << ans << endl;
}

signed main() {
	ios::sync_with_stdio(false);
	cin.tie(nullptr), cout.tie(nullptr);
	int T; cin >> T;
	while (T--) solev();
	return 0;
}
posted @ 2024-05-23 12:26  RainPPR  阅读(122)  评论(0编辑  收藏  举报