原题传送门
给定一个长度为 n n n的序列 A ( n ≤ 200000 ) A (n \leq 200000) A(n≤200000),要求支持单点修改和区间加,并在线询问函数 S ( l , r ) m o d    1 0 9 S(l,r)\mod 10^9 S(l,r)mod109的值,其中函数 S ( l , r ) S(l,r) S(l,r)的定义如下:
S ( l , r ) = ∑ i = 0 r − l A i + l f i f 为 斐 波 那 契 数 列 , f 0 = f 1 = 1 , f i = f i − 1 + f i − 2 S(l,r)=\sum_{i=0}^{r-l}A_{i+l}f_i \\ f为斐波那契数列,f_0=f_1=1,f_i=f_{i-1}+f_{i-2} S(l,r)=i=0∑r−lAi+lfif为斐波那契数列,f0=f1=1,fi=fi−1+fi−2
首先一眼数据结构题,两眼线段树题。
涉及到序列上的斐波那契数列操作,又要动态计算,第一想法是用线段树来维护矩阵,然后用矩阵乘法快速算出斐波那契数列的值。这样做当然是可行的(而且好像也是这道题最主流的做法),但是码量稍大,并且需要封装一个矩阵结构体。对于我这种想偷懒的矩阵菜鸡很不友好。
那么有没有什么避开矩阵的方法呢?当然是有的,不过在正事开始之前需要先介绍一个十分重要的引理:
f i = f k f i − k + f k − 1 f i − k − 1 ( 1 ≤ k < i ) f_i=f_kf_{i-k}+f_{k-1}f_{i-k-1} \qquad (1 \leq k < i) fi=fkfi−k+fk−1fi−k−1(1≤k<i)
实际上感性理解一下即可,也可以用类似数学归纳法的方法来证明:
显 然 当 i ≤ 1 时 结 论 成 立 那 么 当 i > 1 时 , 我 们 假 设 f i − 1 和 f i − 2 都 满 足 性 质 那 么 我 们 选 取 一 个 k ( 1 ≤ k < i − 2 , 因 为 当 k ≥ i − 2 时 结 论 显 然 成 立 ) f i = f i − 1 + f i − 2 = f k f i − k − 1 + f k − 1 f i − k − 2 + f k f i − k − 2 + f k − 1 f i − k − 3 = f k ( f i − k − 1 + f i − k − 2 ) + f k ( f i − k − 2 + f i − k − 3 ) = f k f i − k + f k − 1 f i − k − 1 ∴ 结 论 成 立 显然当i\leq1时结论成立 \\ 那么当i>1时,我们假设f_{i-1}和f_{i-2}都满足性质 \\ 那么我们选取一个k \quad (1\leq k < i-2,因为当k\geq i-2时结论显然成立)\\ \begin{aligned} f_i&=f_{i-1}+f_{i-2} \\ &=f_kf_{i-k-1}+f_{k-1}f_{i-k-2}+f_kf_{i-k-2}+f_{k-1}f_{i-k-3}\\ &=f_k(f_{i-k-1}+f_{i-k-2})+f_k(f_{i-k-2}+f_{i-k-3}) \\ &=f_kf_{i-k}+f_{k-1}f_{i-k-1}\\ \therefore 结论成立 \end{aligned} 显然当i≤1时结论成立那么当i>1时,我们假设fi−1和fi−2都满足性质那么我们选取一个k(1≤k<i−2,因为当k≥i−2时结论显然成立)fi∴结论成立=fi−1+fi−2=fkfi−k−1+fk−1fi−k−2+fkfi−k−2+fk−1fi−k−3=fk(fi−k−1+fi−k−2)+fk(fi−k−2+fi−k−3)=fkfi−k+fk−1fi−k−1
这个引理有什么作用呢?我们可以举个栗子来感受一下:
假设我们操作前的 S 1 S_1 S1为 A 1 f 0 + A 2 f 1 + A 3 f 2 A_1f_0+A_2f_1+A_3f_2 A1f0+A2f1+A3f2,并且我们知道 S 2 = A 1 f 1 + A 2 f 2 + A 3 f 3 S_2=A_1f_1+A_2f_2+A_3f_3 S2=A1f1+A2f2+A3f3的值,我们现在想要把这个序列的斐波那契系数平移 4 4 4个单位,也就是说把最终的 S S S变为 A 1 f 4 + A 2 f 5 + A 3 f 6 A_1f_4+A_2f_5+A_3f_6 A1f4+A2f5+A3f6,那么我们可以进行如下操作:先把 S 1 S_1 S1乘上 f 2 f_2 f2,再把 S 2 S_2 S2乘上 f 3 f_3 f3,然后把它们加起来,就可以得到最终的 S S S。具体用柿子表示就是:
S 1 f 2 + S 2 f 3 = A 1 f 0 f 2 + A 2 f 1 f 2 + A 3 f 2 f 2 + A 1 f 1 f 3 + A 2 f 2 f 3 + A 3 f 3 f 3 = A 1 ( f 0 f 2 + f 1 f 3 ) + A 2 ( f 1 f 2 + f 2 f 3 ) + A 3 ( f 2 f 2 + f 3 f 3 ) = A 1 f 4 + A 2 f 5 + A 3 f 6 ( 运 用 引 理 ) = S \begin{aligned} S_1f_2+S_2f_3&=A_1f_0f_2+A_2f_1f_2+A_3f_2f_2+A_1f_1f_3+A_2f_2f_3+A_3f_3f_3\\ &=A_1(f_0f_2+f_1f_3)+A_2(f_1f_2+f_2f_3)+A_3(f_2f_2+f_3f_3) \\ &=A_1f_4+A_2f_5+A_3f_6 \qquad (运用引理)\\ &=S \end{aligned} S1f2+S2f3=A1f0f2+A2f1f2+A3f2f2+A1f1f3+A2f2f3+A3f3f3=A1(f0f2+f1f3)+A2(f1f2+f2f3)+A3(f2f2+f3f3)=A1f4+A2f5+A3f6(运用引理)=S
再抽象一点来讲,如果我们要将 S 1 S_1 S1的系数平移 k k k个单位,最终得到的 S S S即为 S 1 f k − 2 + S 2 f k − 1 S_1f_{k-2}+S_2f_{k-1} S1fk−2+S2fk−1。
于是我们就可以在线段树上的每个节点维护相应区间的 S 1 S_1 S1和 S 2 S_2 S2值,在向上合并的时候把右儿子平移一段距离再加上左儿子即可(查询时同理);区间修改的时候打上标记,向下 p u s h _ d o w n push\_down push_down的时候,我们发现整段区间实际上是加上了 t a g tag tag倍的一段斐波那契数列的前缀和。于是我们预处理斐波那契数列的前缀和即可。
模数写成1e9+7调了半个小时
#include
#define ll long long
#define P ((ll)1e9)
#define MAX 200005
#define lc(x) (x<<1)
#define rc(x) (x<<1|1)
#define mid ((l+r)>>1)
using namespace std;
int n, m;
ll f[MAX], sum[MAX];
void init(){
f[0] = f[1] = 1;
sum[0] = 1, sum[1] = 2;
for(int i = 2; i < MAX; ++i){
f[i] = (f[i-1]+f[i-2])%P;
sum[i] = (sum[i-1]+f[i])%P;
}
}
ll s1[MAX*4], s2[MAX*4], tag[MAX*4];
ll calc(ll p, ll st){ //p节点代表的区间,第一项系数从f_st开始的和
if(st == 0) return s1[p];
else if(st == 1) return s2[p];
else return (s1[p]*f[st-2]%P+s2[p]*f[st-1]%P) % P;
}
void push_up(int p, int l, int r){
s1[p] = (s1[lc(p)]+calc(rc(p), mid-l+1)) % P;
s2[p] = (s2[lc(p)]+calc(rc(p), mid-l+2)) % P;
}
void add(int p, int l, int r, ll k){
(tag[p] += k) %= P;
(s1[p] += sum[r-l]*k%P) %= P;
(s2[p] += (sum[r-l+1]+P-1)%P*k%P) %= P;
}
void push_down(int p, int l, int r){
if(!tag[p]) return;
add(lc(p), l, mid, tag[p]);
add(rc(p), mid+1, r, tag[p]);
tag[p] = 0;
}
void update(int p, int l, int r, int ul, int ur, ll k){
if(l >= ul && r <= ur){
add(p, l, r, k);
return;
}
push_down(p, l, r);
if(mid >= ul) update(lc(p), l, mid, ul, ur, k);
if(mid < ur) update(rc(p), mid+1, r, ul, ur, k);
push_up(p, l, r);
}
void modify(int p, int l, int r, int u, ll k){
if(l == r){
s1[p] = s2[p] = k;
return;
}
push_down(p, l, r);
if(mid >= u) modify(lc(p), l, mid, u, k);
else modify(rc(p), mid+1, r, u, k);
push_up(p, l, r);
}
ll query(int p, int l, int r, int ul, int ur){
if(l >= ul && r <= ur){
return calc(p, l-ul);
}
push_down(p, l, r);
ll res = 0;
if(mid >= ul) (res += query(lc(p), l, mid, ul, ur)) %= P;
if(mid < ur) (res += query(rc(p), mid+1, r, ul, ur)) %= P;
return res;
}
int main()
{
init();
cin >> n >> m;
int t, x, y, k;
for(int i = 1; i <= n; ++i){
scanf("%d", &x);
modify(1, 1, n, i, x);
}
for(int i = 1; i <= m; ++i){
scanf("%d%d%d", &t, &x, &y);
if(t == 1){
modify(1, 1, n, x, y);
}
else if(t == 2){
printf("%lld\n", query(1, 1, n, x, y));
}
else{
scanf("%d", &k);
update(1, 1, n, x, y, k);
}
}
return 0;
}