CF446C DZY Loves Fibonacci Numbers 题解和加强
简要题意
https://www.luogu.com.cn/problem/CF446C
给定一个长度为 \(n\) 的序列 \(A\),要求支持两种操作:1 给定区间 \((l, r)\) 对这个区间内的每个数,依次加斐波那契数列的前 \(r - l + 1\) 项;2 给定区间求和。
数据范围:\(1\le n \le 3\times10^5, 1 \le q \le 3\times 10^5\)。
加强:给定区间求平方和。
原题思路
斐波那契数列的差分、前缀和还是斐波那契数列,和区间加一次函数、二次函数等有本质区别,经典的差分、前缀和等处理以后数据结构维护的简单思路是行不通的。必须另辟蹊径。
根据斐波那契数列的递推式可以得到它的一个性质:两个斐波那契数列错开一位,相减以后得到的也是一个斐波那契数列,并且这个斐波那契数列相对于原先两个,又向前移动一位,如下所示:
注意到 \(\forall i > 2, F_{i, j} = F_{i - 2, j} - F_{i - 1, j}\),于是所有的 \(F\) 的每一行都可以表示为 \(F_1, F_2\) 的线性组合,且系数可以在 \(\Theta(n)\) 时间递推预处理得到。
这些数组可以帮助我们进行区间加的操作。首先考虑另一个经典模型:给定序列 \(A, B\),\(B\) 始终不变,要求支持一种操作给定 \(l, r\),使 \(\forall l\le i\le r, A_i \leftarrow A_i + kB_i\),维护 \(A\) 的区间和。这直接在线段树上令 tag 是 \(B\) 的系数即可,对于下推标记和区间更新,通过预处理 \(B\) 的前缀和即可做到。如果不止有一个数组,也是一个道理,因为两个数组互不影响。
时间/空间复杂度 \(\Theta(n \log n)/\Theta(n)\)。
原题代码
// Author: kyEEcccccc
#include <bits/stdc++.h>
using namespace std;
using LL = long long;
using ULL = unsigned long long;
#define F(i, l, r) for (int i = (l); i <= (r); ++i)
#define FF(i, r, l) for (int i = (r); i >= (l); --i)
#define MAX(a, b) ((a) = max(a, b))
#define MIN(a, b) ((a) = min(a, b))
#define SZ(a) ((int)((a).size()) - 1)
const int N = 300005, P = 1000000009;
int sf1[N], sf2[N];
struct SegTree
{
int sum[N << 2];
pair<int, int> tg[N << 2];
void update(int p)
{
sum[p] = (sum[p * 2] + sum[p * 2 + 1]) % P;
}
void pushdown(int p, int cl, int cr)
{
int cm = (cl + cr) >> 1;
LL t1 = tg[p].first, t2 = tg[p].second;
tg[p] = {0, 0};
sum[p * 2] = (sum[p * 2] + t1 * (sf1[cm] - sf1[cl - 1] + P)) % P;
sum[p * 2] = (sum[p * 2] + t2 * (sf2[cm] - sf2[cl - 1] + P)) % P;
sum[p * 2 + 1] = (sum[p * 2 + 1] + t1 * (sf1[cr] - sf1[cm] + P)) % P;
sum[p * 2 + 1] = (sum[p * 2 + 1] + t2 * (sf2[cr] - sf2[cm] + P)) % P;
(tg[p * 2].first += t1) %= P;
(tg[p * 2].second += t2) %= P;
(tg[p * 2 + 1].first += t1) %= P;
(tg[p * 2 + 1].second += t2) %= P;
}
void add(int p, int cl, int cr, int l, int r, LL x1, LL x2)
{
if (cl >= l && cr <= r)
{
sum[p] = (sum[p] + x1 * (sf1[cr] - sf1[cl - 1] + P)
+ x2 * (sf2[cr] - sf2[cl - 1] + P)) % P;
(tg[p].first += x1) %= P;
(tg[p].second += x2) %= P;
return;
}
pushdown(p, cl, cr);
int cm = (cl + cr) >> 1;
if (l <= cm)
{
add(p * 2, cl, cm, l, r, x1, x2);
}
if (r > cm)
{
add(p * 2 + 1, cm + 1, cr, l, r, x1, x2);
}
update(p);
}
LL get_sum(int p, int cl, int cr, int l, int r)
{
if (cl >= l && cr <= r)
{
return sum[p];
}
pushdown(p, cl, cr);
int cm = (cl + cr) >> 1;
LL ret = 0;
if (l <= cm)
{
ret += get_sum(p * 2, cl, cm, l, r);
}
if (r > cm)
{
ret += get_sum(p * 2 + 1, cm + 1, cr, l, r);
}
return ret % P;
}
} segt;
int n, m, a[N];
pair<int, int> g[N];
int main(void)
{
// freopen(".in", "r", stdin);
// freopen(".out", "w", stdout);
ios::sync_with_stdio(0), cin.tie(nullptr);
cin >> n >> m;
F(i, 1, n)
{
cin >> a[i];
(a[i] += a[i - 1]) %= P;
}
g[1] = {1, 0};
g[2] = {0, 1};
F(i, 3, n)
{
g[i].first = (g[i - 2].first - g[i - 1].first + P) % P;
g[i].second = (g[i - 2].second - g[i - 1].second + P) % P;
}
sf1[1] = sf1[2] = 1;
sf2[1] = 0, sf2[2] = 1;
F(i, 3, n)
{
sf1[i] = (sf1[i - 1] + sf1[i - 2]) % P;
sf2[i] = (sf2[i - 1] + sf2[i - 2]) % P;
}
F(i, 1, n)
{
sf1[i] = (sf1[i - 1] + sf1[i]) % P;
sf2[i] = (sf2[i - 1] + sf2[i]) % P;
}
F(i, 1, m)
{
int op, l, r;
cin >> op >> l >> r;
if (op == 1)
{
segt.add(1, 1, n, l, r, g[l].first, g[l].second);
}
else
{
cout << (segt.get_sum(1, 1, n, l, r)
+ a[r] - a[l - 1] + P) % P << '\n';
}
}
return 0;
}
加强版思路
通过上文对原题的分析,我们可以将区间加斐波那契数列的前若干项,变为加两个斐波那契数列的对应位置项。对于维护平方,也是一个道理:
那么我们只需要预处理 \(B_i^2\) 前缀和,同时维护 \(A_iB_i\) 的区间和即可,又注意到:
而 \(B_iC_i\) 的前缀和也可以预处理,所以加强版问题在这个转化视角下也是容易的。