• 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,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
    ",ans);
                }
            }
        }
        return 0;
    }
  • 相关阅读:
    Java基础之线程最详细最精简的介绍
    Android基础之Android硬件
    Iphone客户端程序员半年工作总结
    Java基础之synchronized的讲解
    物联网能否落地?可裁剪嵌入式OS成关键
    java基础之Thread与Runnable的区别
    Android基础之小问题集锦
    Java基础之字符串匹配大全
    BPM 是与非 什么是BPM,如何辨别是否BPM产品,以及如何选择BPM产品
    Hello China操作系统的安装和使用
  • 原文地址:https://www.cnblogs.com/kkrisen/p/3847376.html
Copyright © 2020-2023  润新知