HZNUOJ ACM实验室暑期考核 F B_M的斐波那契 线段树/树状数组
题意
给一个长度为\(n\) 的\(a\) 数组,初始时\(a_i = 1\) ,有两个操作
- \(l-r\) 区间内\(a_i += 1\)
- 询问\(l-r\) 区间\(a_i\) 作为斐波那契数列下标的数值和是否$ \geq k \quad$ ($ k \leq 10^{10})$
分析
如果直接线段树区间修改显然是做不到的,不妨换一种思路
注意到\(f_{50} \geq 10^{10}\) ,这意味着一个点一旦被加了超过\(50\) 次,就没必要修改这个点了。
这样就考虑怎么高效的删除这些不需要的点。
考虑用set 存放\(1-n\) 的下标,一旦某个下标的斐波那契数列项数超过了\(50\) 就删除这个节点。这样一来
就是一个单点修改(注意一个点最多被修改50次) ,区间求和
于是就可以用树状数组或者线段树维护了
ll fib[55] = { 0, 1 };
int n, m;
int num[maxn << 2];
ll sum[maxn << 2];
set<int> st;
void pushUp(int i) {
sum[i] = sum[i << 1] + sum[i << 1 | 1];
}
void update(int pos, int l, int r, int i) {
if (l == pos && r == pos) {
num[i]++;
sum[i] = fib[num[i]];
if (num[i] == 50) st.erase(pos);
return;
}
int mid = l + r >> 1;
if (pos <= mid) update(pos, l, mid, i << 1);
else update(pos, mid + 1, r, i << 1 | 1);
pushUp(i);
}
int res;
void query(int l, int r, int i, int L, int R) {
if (L <= l && R >= r) {
res += sum[i];
return;
}
int mid = l + r >> 1;
if (L <= mid) query(l, mid, i << 1, L, R);
if (R > mid) query(mid + 1, r, i << 1 | 1, L, R);
}
void range_update(int l, int r) {
auto begin = st.lower_bound(l);
auto end = st.upper_bound(r); //这里用了upper -1就一定是范围里了
if (end != st.end()) end--;
for (auto it = begin; it != end; it++) {
update(*it, 1, n, 1);
}
}
int main() {
for (int i = 2; i < 55; i++) fib[i] = fib[i - 1] + fib[i - 2];
scanf("%d%d", &n, &m);
for (int i = 1; i <= n; i++) update(i, 1, n, 1), st.insert(i);
while (m--) {
int op;
scanf("%d", &op);
if (op == 1) {
int l, r;
scanf("%d%d", &l, &r);
range_update(l, r);
}
else {
int l, r;
ll k;
scanf("%d%d%lld", &l, &r, &k);
res = 0;
query(1, n, 1, l, r);
puts(k <= res ? "YES" : "NO");
}
}
return 0;
}