• coderfoces446c (斐波那契数列)


    题目描述:

      区间增值,但是每一项增加的值为Fi - l + 1,F[i]为斐波那契数列,求区间和?

    考虑线段树,刚开始想用斐波那契数列的前n项和,可是推不出来,考虑到每个区间的增值序列都是一段斐波那契数列,他们的和是否有什么特性呢?

    发现如果前两项为a和b的话,那么,a,b,a+b,a+2b,2a+3b,3a+5b;

    a和b前的系数为斐波那契数列(后一项为前两项之和,

    设F[k]表示以a,b开头的第k项的值,s[k]代表以a和b开头的前k项和

    F[k]=a*f[k-2]+b*f[k-1];

    F[1]=1*a+0*b;

    F[2]=0*a+1*b;

    F[3]=f[1]*a+f[2]*b;

    F[4]=f[2]*a+f[3]*b;

    F[k]=f[k-2]*a+f[k-1]*b;

    pp[k]=1+0+f[1]+f[2]+f[3]+...f[k-2];

    qq[k]=0+f[1]+f[2]+f[3]+...+f[k-1];

    求和:

    S[k]=a*pp[k]+b*qq[k];

    这样只需要确定每个区间的ab,长度可以计算出来,那么第k项可以求出来,k项和也可以求出来;

    维护每个区间的ab的值,ab作为标记。

    写这道题是发现对标记的处理有了更深的理解:

     标记会有那些操作呢?

     1 位置,最下层的标记以下的节点没有被更新,最上层的标记以上全都被更新过,

     也就是对于每一个标记来说,它没有更新它所在节点的子节点,更新了它所有的父节点。

     2 标记在同一个区间可以累加(累加型标记)

     3 标记传递时,子节点的值是由父节点的标记累计改变的,子节点的标记只是用来往下传的,所以子节点的标记值没有用。

    #include <iostream>
    #include <cstdio>
    #include <cstring>
    #include <cmath>
    #include <cstring>
    #include <algorithm>
    #define LL long long
    #define MOD 1000000009
    using namespace std;
    //线段树
    //区间每点增值,求区间和
    const int maxN = 310000;
    struct node
    {
        int  lt, rt;
        int  addA,addB;
        LL   val;
    }tree[4*maxN];
    LL  a[maxN];
    int n,m;
    LL f[maxN];
    LL pp[maxN];
    LL qq[maxN];
    void init()
    {
        memset(f,0,sizeof(f));
        f[1]=1; f[2]=1;
        pp[1]=1; pp[2]=1;
        qq[1]=0; qq[2]=1;
        for(int i=3;i<maxN;i++)
        {
            f[i]=(f[i-1]+f[i-2])%MOD;
            pp[i]=(pp[i-1]+f[i-2])%MOD;
            qq[i]=(qq[i-1]+f[i-1])%MOD;
        }
    }
    //向下更新
    void pushDown(int id)
    {
        if (tree[id].addA != 0 || tree[id].addB!=0)
        {
            LL a,b;
            int LeftLen= tree[id << 1].rt - tree[id << 1].lt +1;
            a=tree[id].addA;  b=tree[id].addB;
            tree[id<<1].addA +=  a;
            tree[id<<1].addB +=  b;
            tree[id<<1].addA %=MOD;
            tree[id<<1].addB %=MOD;
            tree[id<<1].val  += ( ( (pp[LeftLen]* a)%MOD + (qq[LeftLen] *b)%MOD )%MOD ) ;
            tree[id<<1].val  %= MOD;
    
            int RightLen= tree[id << 1 |1].rt - tree[id <<1 |1].lt +1;
            a=( ( (tree[id].addA * f[LeftLen+ 1-2] )%MOD + (tree[id].addB * f[LeftLen +1 -1])%MOD ) %MOD );
            b=( ( (tree[id].addA * f[LeftLen+1-2 +1])%MOD + (tree[id].addB * f[LeftLen +1 -1 +1])%MOD )%MOD);
            //a和b分别为第k项和第k+1项
            tree[id<<1 |1].addA +=a;
            tree[id<<1 |1].addB +=b;
            tree[id <<1 |1].addA%=MOD;
            tree[id <<1 |1].addB%=MOD;
            tree[id<<1 |1].val  +=( ( (pp[RightLen] *a) %MOD + (qq[RightLen] *b)%MOD ) %MOD );
            tree[id <<1 |1].val%=MOD;
            tree[id].addA = 0;
            tree[id].addB = 0;
        }
    }
    
    //向上更新
    void pushUp(int id)
    {
        tree[id].val = ( (tree[id<<1].val + tree[id<<1|1].val) %MOD);
    }
    
    //建立线段树
    void build(int lt, int rt, int id)
    {
        tree[id].lt = lt;
        tree[id].rt = rt;
        tree[id].val =  0;//每段的初值,根据题目要求
        tree[id].addA = 0;
        tree[id].addB = 0;
        if (lt == rt)
        {
            tree[id].val = a[lt];
            return;
        }
        int mid = (lt+rt)>>1;
        build(lt, mid, id<<1);
        build(mid+1, rt, id<<1|1);
        pushUp(id);
    }
    
    //增加区间内每个点固定的值
    void add2(int lt, int rt, int id, int Left)
    {
        if (lt <= tree[id].lt && rt >= tree[id].rt)
        {
            int plsa=   tree[id].lt - Left +1;
            int plsb=   tree[id].lt - Left +2;
            tree[id].addA +=  f[plsa];
            tree[id].addB +=  f[plsb];
            tree[id].addA%=MOD;
            tree[id].addB%=MOD;
            LL a,b;
            a=f[plsa]; b=f[plsb];
            int Len= tree[id].rt - tree[id].lt + 1;
            tree[id].val  +=( (a*pp[Len])%MOD + (b*qq[Len])%MOD )%MOD ;
            tree[id].val %=MOD;
            return;
        }
        pushDown(id);
        //区间更新中最重要的lazy操作,把下次可能要查询的节点的标记更新到,然后只要不影响查询就好。
        int mid = (tree[id].lt+tree[id].rt)>>1;
        if (lt <= mid)
            add2(lt, rt, id<<1, Left);
        if (rt > mid)
            add2(lt, rt, id<<1|1, Left);
        pushUp(id);
    }
    
    //查询某段区间内的和
    LL query(int lt, int rt, int id)
    {
        if (lt <= tree[id].lt && rt >= tree[id].rt)
            return tree[id].val;
        pushDown(id);
        //如果不pushdown的话,它的计算方式是记下沿途的标记,到达目的节点之后计算目的节点自上而下的标记之和;
        //然后再加上本节点之前由自下而上的标记递推的来的和也就是tree.val(tree.val的含义就是只执行节点id及其子孙节点中的add操作,节点id对应区间中所有数之和)
        //如果每次pushdown就可以把下次可能要查询的节点的标记更新到就好(tree.val的含义就是执行所有对节点id有影响的id操作,节点id对应区间中所有数之和)
        int mid = (tree[id].lt+tree[id].rt)>>1;
        LL ans = 0;
        if (lt <= mid)
            ans += query(lt, rt, id<<1);
        if (rt > mid)
            ans += query(lt, rt, id<<1|1);
        ans%=MOD;
        return ans;
    }
    int main()
    {
       //freopen("test.txt","r",stdin);
        init();
        while(~scanf("%d%d",&n,&m))
        {
           for(int i=1;i<=n;i++)
              scanf("%d",&a[i]);
              build(1,n,1);
           for(int i=1;i<=m;i++)
           {
               int c,l,r;
               scanf("%d%d%d",&c,&l,&r);
               if(c==1)
               {
                   add2(l,r,1,l);
               }
               if(c==2)
               {
                   printf("%I64d
    ",query(l,r,1));
               }
           }
        }
        return 0;
    }
  • 相关阅读:
    OCP-1Z0-053-V12.02-541题
    OCP-1Z0-053-V12.02-544题
    OCP-1Z0-053-V12.02-545题
    OCP-1Z0-053-V13.02-711题
    OCP-1Z0-053-V12.02-493题
    OCP-1Z0-053-V13.02-696题
    OCP-1Z0-053-V12.02-522题
    OCP-1Z0-053-V12.02-523题
    OCP-1Z0-053-V12.02-534题
    OCP-1Z0-053-V13.02-692题
  • 原文地址:https://www.cnblogs.com/xianbin7/p/4878220.html
Copyright © 2020-2023  润新知