📅  最后修改于: 2023-12-03 15:26:12.447000             🧑  作者: Mango
本文将介绍如何在一个大小为n的数组中,进行范围查询求斐波纳契数的个数,并统计数组的更新次数。本文采用C++编写示例程序。
给定一个大小为n的数组a,以及m个询问,每个询问包含两个整数 l 和 r,要求计算在a[l:r]这段区间中,有多少个斐波纳契数(斐波那契数列:1,1,2,3,5,8,13,21...,即前两项为1,从第三项开始,每一项都是前面两项的和)。
同时,需要记录在处理这些询问的过程中,一共进行了多少次的数组更新操作。
最简单的方法就是对于每个询问,都遍历区间中的数,判断它是否为斐波纳契数。时间复杂度为 O(m * (r - l) ^ 2),其中m为询问数量,(r - l) ^ 2为区间长度的平方。
此方法的时间复杂度过高,无法通过测试数据。因此,我们需要寻找更高效的算法。
斐波那契数列具有以下性质:
由此可以得到一种思路:对于每个询问,从l开始,逐一计算区间中的数和斐波那契数列比较,直到找到第一个大于区间右端点r的斐波那契数。那么该区间中恰好有k个斐波那契数。
但是,我们注意到:当一个数比斐波那契数大时,它会很快超过斐波那契数,而当一个数比斐波那契数小时,需要很长时间才能达到斐波那契数。
因此,我们可以先预处理出一些小于数组最大值的斐波那契数,如此一来,可以在一个区间中,直接通过二分查找得到第一个大于r的斐波那契数,使得时间复杂度为 O(m * logn)。额外的空间复杂度为 O(n)。
注意到这道题中,所求得的斐波那契数只涉及到区间端点和区间长度,因此可以直接使用斐波那契数列来替换斐波那契数的计算,在维护区间最小值时,同时计算区间斐波那契数的个数。
而计数区间斐波那契数的个数,可以借鉴区间最小值线段树求和的方式进行。时间复杂度为O(m * logn),额外的空间复杂度为 O(n)。
#include <bits/stdc++.h>
using namespace std;
typedef long long LL;
const int MAXN = 1e5 + 10;
struct Segment_Tree {
LL val[MAXN << 2], add[MAXN << 2], fib[MAXN];
void push_up(LL o) {
val[o] = val[o << 1] + val[o << 1 | 1];
}
void push_down(LL o, LL l, LL r) {
if (add[o]) {
LL mid = (l + r) >> 1;
val[o << 1] += fib[mid - l + 1] * add[o];
add[o << 1] += add[o];
val[o << 1 | 1] += fib[r - mid] * add[o];
add[o << 1 | 1] += add[o];
add[o] = 0;
}
}
void build(LL o, LL l, LL r) {
if (l == r) {
val[o] = 0;
return;
}
LL mid = (l + r) >> 1;
build(o << 1, l, mid);
build(o << 1 | 1, mid + 1, r);
push_up(o);
}
void update(LL o, LL l, LL r, LL ql, LL qr, LL v) {
if (ql <= l && r <= qr) {
add[o] += v;
val[o] += v * fib[r - l + 1];
return;
}
push_down(o, l, r);
LL mid = (l + r) >> 1;
if (ql <= mid) update(o << 1, l, mid, ql, qr, v);
if (qr > mid) update(o << 1 | 1, mid + 1, r, ql, qr, v);
push_up(o);
}
LL query(LL o, LL l, LL r, LL ql, LL qr) {
if (ql <= l && r <= qr) {
return val[o];
}
push_down(o, l, r);
LL mid = (l + r) >> 1;
LL ret = 0;
if (ql <= mid) ret += query(o << 1, l, mid, ql, qr);
if (qr > mid) ret += query(o << 1 | 1, mid + 1, r, ql, qr);
push_up(o);
return ret;
}
}tree;
int n, m, a[MAXN];
void init() {
tree.fib[0] = tree.fib[1] = 1;
for (int i = 2; i <= n; i++) {
tree.fib[i] = tree.fib[i - 1] + tree.fib[i - 2];
if (tree.fib[i] >= ((LL)1 << 31)) break;
}
}
int main() {
// input n, m, a[n]
init();
tree.build(1, 1, n);
for (int i = 1; i <= m; i++) {
// input l,r
tree.update(1, 1, n, l, r, 1);
printf("%lld\n", tree.query(1, 1, n, l, r));
}
return 0;
}
变量:
val
: 用于存储区间和结果的数组;add
: 用于存储当前节点的延迟标记;fib
: 用于存储斐波那契数列的数组。函数:
push_up
: 根据左右子树的值,更新当前节点的区间和;push_down
: 将当前节点的延迟标记下传至子节点,并更新子节点的区间和;build
: 建立线段树,区间的和初始化为$0$;update
: 将区间$[ql,qr]$的值全部增加$v$,并更新节点区间和的值;query
: 查询区间$[ql,qr]$的和的值。时间复杂度为$O(m*logn)$。