码迷,mamicode.com
首页 > 其他好文 > 详细

Codeforces 446C 线段树 递推Fibonacci公式

时间:2014-07-16 18:10:06      阅读:270      评论:0      收藏:0      [点我收藏+]

标签:style   blog   color   os   for   io   

聪哥推荐的题目

区间修改和区间查询,但是此题新颖之处就在于他的区间修改不是个定值,而是从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,r
const 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;
}

 

Codeforces 446C 线段树 递推Fibonacci公式,布布扣,bubuko.com

Codeforces 446C 线段树 递推Fibonacci公式

标签:style   blog   color   os   for   io   

原文地址:http://www.cnblogs.com/kkrisen/p/3847376.html

(0)
(0)
   
举报
评论 一句话评论(0
登录后才能评论!
© 2014 mamicode.com 版权所有  联系我们:gaon5@hotmail.com
迷上了代码!