jzoj4724. 斐波那契

Description

DJL为了避免成为一只咸鱼,来找czgj学习Fibonacci数列。
通过czgj的谆谆教导,DJL明白了Fibonacci数列是这样定义的:
F(1)=1;F(2)=1;F(n)=F(n-1)+F(n-2)(n>2)
Czgj深谙熟能生巧的道理,于是他给了DJL一个数列,并安排了如下的训练计划:
1、“1 L r”,表示给ai 加上F(i-L+1) ,其中L<=i<=r ;
2、“2 L r”,表示询问a中l到r的值的和mod 1000000009 的值。
DJL经过长时间的学习,感觉身体被掏空,他希望你能帮他解决这个问题。

Input

第一行两个整数n和m,表示原始数列的长度,和总的训练次数。
第二行n个整数a1,a2,…,an(1<=ai<=10^9) ,表示czgj给DJL的原始数列。
接下来m行,每一行给出三个整数,表示问题描述中的两种训练的一种。保证1<=L<=r<=n 。

Output

对于每一种形如“2 L r”的训练,输出一行一个整数值。

Sample Input

4 4
1 2 3 4
1 1 4
2 1 4
1 2 4
2 1 3

Sample Output

17
12
样例解释
经过第一次操作,数列变为a=[2,3,5,7] ;
第二次询问,sum=2+3+5+7=17 ;
经过第三次操作,数列变为a=[2,4,6,9] ;
第四次询问,sum=2+4+6=12 。

Data Constraint

对于20%的数据,1≤n, m≤100;
对于40%的数据,1≤n, m≤1000;
对于100%的数据,1≤n, m≤100000。

Hint

这里写图片描述

题解

无脑方法:直接暴力。
首先,上面的提示其实很好用。
我们看完题目第一直觉是用线段树来直接维护,然而,直接是不对的,我们需要用到提示。
但是,如果你是一位数学硬伤的人士,可以看看这种方法:
我们考虑一个定期重构的方法。(不是替罪羊树)
首先,设做了k次操作后重构。
重构什么意思呢?也就是把a数组给更新一下。
那不重构的话怎么弄呢?
我们设一个d数组,表示一个记录修改操作的数组。这个数组可以利用差分的方式来维护。
什么意思呢?对于一次插入操作l,r
我们直接在d[l]的位置加1
在d[r+1]的位置加上f[r-l+2](斐波那契数列)
在d[r+2]的位置加上f[r-l+1]
这样我们一条线扫过去,每次把d做一遍类似斐波那契的方法就可以求出若干次操作后对a的影响为什么。
也就是d[i]:=d[i]+d[i-1]+d[i-2];
这样的话可以在重构的时候在n的时间复杂度内更新a数组。

那么对于一个插入操作l,r
我们可以先在a中提取值,然后再在未重构的插入操作里面计算。
那么我们就直接暴力枚举未重构的插入操作,然后更新答案即可。

正确性显然,但是时间复杂度好像很玄学。
细细推就是:O(mn/k+km)
然后我们设k为根号n,那么就是√n的时间复杂度。

标程:

uses math;
const up=1000000;
var
        i,j,k,l,r,n,m,sx,t,now:longint;
        a,x,y,kind,sum,fei,sumf,d:array[0..up] of int64;
        mo,ans:int64;
begin
        assign(input,'fibonacci.in');reset(input);
        assign(output,'fibonacci.out');rewrite(output);
        mo:=1000000009;
        fei[1]:=1;
        fei[2]:=1;
        sumf[1]:=1;
        sumf[2]:=2;
        for i:=3 to up do
        begin
                fei[i]:=(fei[i-1]+fei[i-2]) mod mo;
                sumf[i]:=(sumf[i-1]+fei[i]) mod mo;
        end;
        readln(n,m);
        for i:=1 to n do
        begin
                read(a[i]);
                sum[i]:=(sum[i-1]+a[i]) mod mo;
        end;
        readln;
        for i:=1 to m do
        begin
                readln(kind[i],x[i],y[i]);
        end;
        sx:=trunc(sqrt(m));
        now:=1;
        for i:=1 to m do
        begin
                if kind[i]=2 then
                begin
                        ans:=(sum[y[i]]-sum[x[i]-1]+mo) mod mo;
                        for j:=now to i do
                        begin
                                if kind[j]=1 then
                                begin
                                        if (y[j]>=x[i]) and (x[j]<=y[i]) then
                                        begin
                                                ans:=(ans+sumf[min(y[i],y[j])-x[j]+1]-sumf[max(x[i],x[j])-x[j]]+mo) mod mo;
                                        end;
                                end;
                        end;
                        writeln(ans);
                end
                else
                if kind[i]=1 then
                begin
                        inc(d[x[i]]);
                        dec(d[y[i]+1],fei[y[i]-x[i]+2]);
                        dec(d[y[i]+2],fei[y[i]-x[i]+1]);
                end;
                if i mod sx=0 then
                begin
                        a[1]:=a[1]+d[1];
                        for j:=2 to n do
                        begin
                                d[j]:=(d[j]+d[j-1]+d[j-2]+mo) mod mo;
                                a[j]:=(a[j]+d[j]+mo) mod mo;
                        end;
                        for j:=1 to n do
                        begin
                                sum[j]:=(sum[j-1]+a[j]) mod mo;
                        end;
                        fillchar(d,sizeof(d),0);
                        now:=now+sx;
                end;
        end;
end.

然而,√n算法没那么优秀对吧?
那么我就来讲讲神奇的log 算法
于是乎,我们就回到提示之中去:
我们看到前两个,那么我们就可以发现下面的这些东东:
这里写图片描述
是不是很神奇。
于是乎,我们可以发现,斐波那契的公比是相等的。
也就是说可以下面这样:
这里写图片描述
上下两个东东可以相加的。
这个时候再考虑线段树是不是比较清晰明了了?
但,实现起来还是有两个东东卡死了——
1、假如多个操作区间重叠在一起,如何快速计算重叠在一起的答案
2、而且是不是每次下传就要把这几个区间的左端点一齐记录,不然时空都很玄
然后,如果你会用第三个提示,那就用。
可是第四个提示十分清晰明了地可以解决这两个问题:
根据提示,我们发现,可以由最前面的两个推出后面的东东。
那么就计录出前两项即可。
由于是可以加起来的,所以不必担心第二个问题。

这个时候考虑线段树就极好了。
但是我没有码

还有一种线段树维护矩阵的方法。
597大爷提出来的,好像很神奇,可以自己思考思考。
我不会

我活在这夜里。无论周围多么黑暗,我都要努力发光!我相信着,终有一天,我会在这深邃的夜里,造就一道最美的彩虹。
原文地址:https://www.cnblogs.com/RainbowCrown/p/11148393.html