【挖坑记】 JZOJ 4724 斐波那契

题目大意

有一个斐波那契数列:F(1)=1;F(2)=1;F(n)=F(n-1)+F(n-2)(n>2);
也有一个序列长度为n的A;
还有两种操作:
1、“1 L r”,表示给ai 加上F(i-L+1) ,其中L<=i<=r ;
2、“2 L r”,表示询问 这里写图片描述的值。

n,m<=100000,a[i]<=1e9
时间限制4s
空间限制256M

解题思路

这题做得我满脑子都是尼克杨的问号,一定要写一写。

如果区间修改的不是斐波那契数的话,就是很简单的线段树;而现在就要通过特别的方式来使用线段树。

这棵线段树的tag是一个二元组(a,b),表示要加上去的斐波那契数列的前两项,至于怎么用前两项和区间长度快速求和呢,来看:

斐波那契第二项之后的数都是由前两个数乘不同的系数得来的,所以我们可以先得出这两个系数,就是说比如f[i]=f[1]*a+f[2]*b,我们先求a和b。
f[1]=1*f[1]+0*f[2];
f[2]=0*f[1]+1*f[2];
f[3]=1*f[1]+1*f[2];
f[4]=1*f[1]+2*f[2];
这样推一下就会发现,f[1]和f[2]的系数也可以构成斐波那契数列,递推预处理一下即可,对a和b分别做前缀和,就可以O(1)知道一个区间内有多少个f[1]和f[2];

至于线段树的实现细节,我们来看一条结论:
对于类斐波那契数列H1=a, H2=b,满足:
1、 Hn = a ∗ Fn−2 + b ∗ Fn−1
2、H1 + H2 + … + Hn = Hn+2 − b
用这个结合之前的前缀和可以直接求出H中的任意一项。
这一条在Pushdown的时候用得到。

最后贴代码

#include<cstdio>
#include<cstring>
#include<algorithm>
#define maxn 100006
#define fr(i,a,b) for(i=a;i<=b;i++)
using namespace std;
typedef long long ll;
const ll ding=1e9+9;

struct nod
{
    ll a,b;
} c[maxn*4];
int i,n,m,z,l,r,a[maxn];
ll s,f[3][maxn],pr[3][maxn],tr[maxn*4];
void pre(int n)
{
    f[1][1]=1,f[1][2]=0;
    f[2][1]=0,f[2][2]=1;
    pr[1][1]=1,pr[1][2]=1;
    pr[2][1]=0,pr[2][2]=1;
    int i,j;
    fr(i,3,n)
    {
        fr(j,1,2)
        {
            f[j][i]=(f[j][i-1]+f[j][i-2])%ding;
            pr[j][i]=(pr[j][i-1]+f[j][i])%ding;
        }
    }
    return;
}
void maket(int v,int l,int r)
{
    if (l==r)
    {
        tr[v]=a[l];
        return;
    }
    int m=(l+r) >> 1;
    maket(v+v,l,m);
    maket(v+v+1,m+1,r);
    tr[v]=(tr[v+v]+tr[v+v+1])%ding;
    return;
}
ll calc(ll a,ll b,int l)
{
    if (l==1) return a;
    return (pr[1][l]*a%ding+pr[2][l]*b%ding)%ding;
}
ll get(ll a,ll b,ll n)
{
    if (!n) return 0;
    if (n==1) return a;
    if (n==2) return b;
    if (n==3) return a+b;
    return (calc(a,b,n-2)+b)%ding;
}
void update(int v,int st,int en)
{
    if (!c[v].a && !c[v].b) return;
    ll a=c[v].a,b=c[v].b;
    int m=(st+en) >> 1;
    tr[v+v]=(tr[v+v]+calc(a,b,m-st+1))%ding;

    ll a_=get(a,b,m-st+2),b_=get(a,b,m-st+3);
    tr[v+v+1]=(tr[v+v+1]+calc(a_,b_,en-m))%ding;

    c[v+v].a=(c[v+v].a+a)%ding;
    c[v+v].b=(c[v+v].b+b)%ding;
    c[v+v+1].a=(c[v+v+1].a+a_)%ding;
    c[v+v+1].b=(c[v+v+1].b+b_)%ding;

    c[v].a=c[v].b=0;
    return;
}
void modify(int v,int st,int en,int l,int r,nod x)
{
    if (st==l && en==r)                                   
    {
        ll tt;
        tt=calc(x.a,x.b,en-st+1);
        tr[v]=(tr[v]+tt)%ding;
        c[v].a=(c[v].a+x.a)%ding;
        c[v].b=(c[v].b+x.b)%ding;
        return;
    }
    update(v,st,en);
    int m=(st+en) >> 1;
    if (r<=m) modify(v+v,st,m,l,r,x);
    else if (l>m) modify(v+v+1,m+1,en,l,r,x);
    else
    {
        nod vv;
        if (m-l+1>=2)
        {
            vv.a=get(x.a,x.b,m-l+2);
            vv.b=get(x.a,x.b,m-l+3);
        } else vv.a=x.b,vv.b=x.a+x.b;
        modify(v+v,st,m,l,m,x);
        modify(v+v+1,m+1,en,m+1,r,vv);
    }
    tr[v]=(tr[v+v]+tr[v+v+1])%ding;
    return;
}
void findd(int v,int st,int en,int l,int r)
{
    if (st==l && en==r)
    {
        s=(s+tr[v])%ding;
        return;
    }
    update(v,st,en);
    int m=(st+en) >> 1;
    if (r<=m) findd(v+v,st,m,l,r);
    else if (l>m) findd(v+v+1,m+1,en,l,r);
    else
    {
        findd(v+v,st,m,l,m);
        findd(v+v+1,m+1,en,m+1,r);
    }
    tr[v]=(tr[v+v]+tr[v+v+1])%ding;
    return;
}
int main()
{
    freopen("czgj3.in","r",stdin);
    freopen("thi.out","w",stdout);
    scanf("%d%d",&n,&m);
    pre(n);
    fr(i,1,n) scanf("%d",&a[i]);
    maket(1,1,n);
    fr(i,1,m)
    {
        scanf("%d%d%d",&z,&l,&r);
        if (z==1)
        {
            nod t;
            t.a=1,t.b=1;
            modify(1,1,n,l,r,t);
        } else
        {
            s=0;
            findd(1,1,n,l,r);
            printf("%lld\n",s);
        }
    }
    return 0;
}
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值