首页 > 代码库 > Codeforces 446C 线段树 递推Fibonacci公式

Codeforces 446C 线段树 递推Fibonacci公式

聪哥推荐的题目

区间修改和区间查询,但是此题新颖之处就在于他的区间修改不是个定值,而是从L 到 R 分别加 F1、F2、。。。Fr-l+1 (F为斐波那契数列)

想了一下之后,觉得用fib的前缀和来解决,每次做懒惰标记记录下当前区间是从哪个L开始加起的,敲了一半之后发现有问题,就跟上次遇到的懒惰标记问题一样,这是个覆盖性的懒惰标记,每次向下传递后,都要先清除孩子的,清除孩子的也有可能要清除son‘s son,所以要一直pushdown下去,否则就会错,但这样就会超时。

能不能有个累加型的标记让我不用pushdown呢,网上都用的什么二次剩余定理,实在不会

后来发现一个博客的做法相当精妙,利用了斐波那契的特性

我们知道 fib总是由两个两个往后推得  则

若当前 数列前两项 为 a 、b,则之后的必为  a+b a+2b 2a+3b 3a+5b

推完发现 只要知道前两项,后面的任意一项都可以马上出来,因为其系数也满足fib数列

令 K=1,0,1,1,2.。。Ki=Ki-1+Ki-2,。

再令 F=0,1,1,2,3.。。为普通fib数列

则知道 前两项为 a,b,可推算出任意一项 n=Kn*a+Gn*b;

同理,我们可以推算出来,知道前两项后,前n项的总和为

Fn*a+Sn*b(S为fib的前缀和)。

这样的话,我只要每次懒惰标记当前区间的前两项,向下传递就会马上得到区间的加值,并且传递给左右孩子的时候,能根据右孩子的区间不同,马上把前两项变为适合右孩子的那两项。。。最重要的是,这个前两项支持累加,也就是累加型懒惰标记,不用彻底向下传递,这真的是极好的

#include <iostream>#include <cstdio>#include <cstring>#define LL __int64#define lson rt<<1,l,mid#define rson rt<<1|1,mid+1,rconst LL M=1000000009;const int N=300000+20;LL F[N],K[N],G[N],A[N];int n,m;LL d[N<<2],f[N<<2],f1[N<<2],f2[N<<2];void init(){    F[0]=G[0]=0;    K[0]=1;    F[1]=1;    G[1]=0;    K[1]=0;    for (int i=2;i<=n+10;i++){        F[i]=F[i-1]+F[i-2];        K[i]=K[i-1]+K[i-2];        G[i]=G[i-1]+F[i-1];        if (F[i]>=M) F[i]%=M;        if (K[i]>=M) K[i]%=M;        if (G[i]>=M) G[i]%=M;    }    for (int i=1;i<=n;i++) scanf("%d",&A[i]);}void up(int rt){    d[rt]=d[rt<<1]+d[rt<<1|1];    if (d[rt]>=M) d[rt]%=M;}void build(int rt,int l,int r){    f[rt]=0;    if (l>=r){        d[rt]=A[l]%M;        return;    }    int mid=(l+r)>>1;    build(lson);    build(rson);    up(rt);}void fix(LL v1,LL v2,int L,int R,int rt,int l,int r);void pushdown(int rt,int l,int r){    if (l>=r) return;    if (!f[rt]) return;    int mid=(l+r)>>1;    fix(f1[rt],f2[rt],l,mid,lson);    LL t1=K[mid-l+1]*f1[rt]%M+K[mid-l+2]*f2[rt]%M;    LL t2=K[mid-l+2]*f1[rt]%M+K[mid-l+3]*f2[rt]%M;    if (t1>=M) t1%=M;    if (t2>=M) t2%=M;    fix(t1,t2,mid+1,r,rson);    f[rt]=0;}void fix(LL v1,LL v2,int L,int R,int rt,int l,int r){    if (L==l && r==R){ //采用这种锁定区间的方法是因为下面有个地方要用L计算,我之前的那种写法会出错        d[rt]+=(v1*F[R-L+1]%M+G[R-L+1]*v2%M)%M;        if (d[rt]>=M) d[rt]%=M;        if (!f[rt]){            f1[rt]=v1%M;            f2[rt]=v2%M;            f[rt]=1;        }        else{            f1[rt]+=v1%M;            f2[rt]+=v2%M;            if (f1[rt]>=M) f1[rt]%=M;            if (f2[rt]>=M) f2[rt]%=M;        }        return;    }    int mid=(l+r)>>1;    pushdown(rt,l,r);    if (R<=mid) fix(v1,v2,L,R,lson);    else if (L>mid) fix(v1,v2,L,R,rson);    else {        fix(v1,v2,L,mid,lson);        LL t1=K[mid-L+1]*v1%M+K[mid-L+2]*v2%M;//这里L要进行计算,每个孩子对应唯一L,所以为什么我要采用这种锁定区间的方式,就是因为这里。以前以为两种锁定区间的方法差不多,现在找到区别了,如果只是简单为了锁定区间,两种都可以用,但是当区间要作为计算条件的时候,要采取这种方法避免错误        LL t2=K[mid-L+2]*v1%M+K[mid-L+3]*v2%M;        if (t1>=M) t1%=M;        if (t2>=M) t2%=M;        fix(t1,t2,mid+1,R,rson);    }    up(rt);}LL query(int L,int R,int rt,int l,int r){    if (L<=l && r<=R){        return d[rt]%M;    }    pushdown(rt,l,r);    int mid=(l+r)>>1;    LL ret1=0,ret2=0;    if (L<=mid) ret1=query(L,R,lson);    if (R>mid) ret2=query(L,R,rson);    return (ret1+ret2)%M;}int main(){    int op,a,b;    while (scanf("%d%d",&n,&m)!=EOF)    {        init();        build(1,1,n);        while (m--)        {            scanf("%d%d%d",&op,&a,&b);            if (op==1){                fix(1,1,a,b,1,1,n);            }            else{                LL ans=query(a,b,1,1,n);                printf("%I64d\n",ans);            }        }    }    return 0;}