CodeForces - 718C Sasha and Array

题面

题意

给出一串数,要求支持以下两个操作:
1.区间加
2.区间查询f(a[i])的和,f(i)表示斐波那契的第i项的值。

做法

对于求f(i)的值,肯定是利用矩阵快速幂,因此f(i)可以表示为(1 1 \n 1 0)^i,这样区间加p就可以转化为区间乘单位矩阵的p次方,而且矩阵乘法满足分配率,这样就可以用线段树来维护。
维护方法:
每个线段树的结点存两个矩阵(区间f(i)的和和懒惰标记),剩余操作与普通线段树基本相同。
注意:不要记录区间加的数值,因为这样每次下传标记都要进行一次矩阵快速幂,时间复杂度多了一个log。

代码

#include
#include
#define ll long long
#define mid ((l+r)>>1)
#define N 100100
#define M 1000000007
using namespace std;

ll n,m,num[N],tt;
struct Jz
{
    ll num[2][2];
    Jz()
    {
        num[0][0]=num[0][1]=num[1][0]=num[1][1]=0;
    }
    init()
    {
        num[0][1]=num[1][0]=0;
        num[0][0]=num[1][1]=1;
    }
    Jz operator + (const Jz &u) const
    {
        ll i,j;
        Jz res;
        for(i=0;i<2;i++)
        {
            for(j=0;j<2;j++)
            {
                res.num[i][j]=num[i][j]+u.num[i][j];
                res.num[i][j]%=M;
            }
        }
        return res;
    }
    Jz operator * (const Jz &u) const
    {
        ll i,j,k;
        Jz res;
        for(i=0;i<2;i++)
        {
            for(j=0;j<2;j++)
            {
                for(k=0;k<2;k++)
                {
                    res.num[i][j]+=num[i][k]*u.num[k][j]%M;
                    res.num[i][j]%=M;
                }
            }
        }
        return res;
    }
    Jz po(ll u)
    {
        Jz res,tmp=*this;
        res.num[0][0]=res.num[1][1]=1;
        for(;u;)
        {
            if(u&1) res=res*tmp;
            tmp=tmp*tmp;
            u>>=1;
        }
        return res;
    }
    void out()
    {
        printf("%lld %lld\n%lld %lld\n",num[0][0],num[0][1],num[1][0],num[1][1]);
    }
};
Jz dw,tmp;
struct Node
{
    ll ls,rs;
    Jz jz,lazy;
};
Node node[N<<1];

void build(ll now,ll l,ll r)
{
    if(l==r)
    {
        node[now].jz=dw.po(num[l]);
        return;
    }
    if(l<=mid)
    {
        node[now].ls=++tt;
        build(tt,l,mid);
        node[now].jz=node[now].jz+node[node[now].ls].jz;
    }
    if(midnow].rs=++tt;
        build(tt,mid+1,r);
        node[now].jz=node[now].jz+node[node[now].rs].jz;
    }
}

inline void down(ll now)
{
    if(node[now].ls)
    {
        node[node[now].ls].lazy=node[node[now].ls].lazy*node[now].lazy;
        node[node[now].ls].jz=node[node[now].ls].jz*node[now].lazy;
    }
    if(node[now].ls)
    {
        node[node[now].rs].lazy=node[node[now].rs].lazy*node[now].lazy;
        node[node[now].rs].jz=node[node[now].rs].jz*node[now].lazy;
    }
    node[now].lazy.init();
}

void add(ll now,ll l,ll r,ll u,ll v)
{
    if(l==u&&v==r)
    {
        node[now].lazy=node[now].lazy*tmp;
        node[now].jz=node[now].jz*tmp;
        return;
    }
    down(now);
    Jz res;
    if(u<=mid)
    {
        add(node[now].ls,l,mid,u,min(v,mid));
    }
    if(midnow].rs,mid+1,r,max(mid+1,u),v);
    }
    if(l<=mid) res=res+node[node[now].ls].jz;
    if(midnow].rs].jz;
    node[now].jz=res;
}

ll ask(ll now,ll l,ll r,ll u,ll v)
{
    if(l==u&&v==r)
    {
        return node[now].jz.num[1][0];
    }
    down(now);
    ll res=0;
    if(u<=mid)
    {
        res+=ask(node[now].ls,l,mid,u,min(mid,v));
    }
    if(midnow].rs,mid+1,r,max(u,mid+1),v);
    }
    return res%M;
}

int main()
{
    dw.num[0][0]=dw.num[0][1]=dw.num[1][0]=1;
    ll i,j,o,p,q,z;
    cin>>n>>m;
    for(i=1;i<=n;i++)
    {
        scanf("%lld",&num[i]);
    }
    build(++tt,1,n);
    for(i=1;i<=tt;i++) node[i].lazy.init();
    for(i=1;i<=m;i++)
    {
        scanf("%lld",&o);
        if(o==1)
        {
            scanf("%lld%lld%lld",&p,&q,&z);
            tmp=dw.po(z);
            add(1,1,n,p,q);
        }
        else
        {
            scanf("%lld%lld",&p,&q);
            printf("%lld\n",ask(1,1,n,p,q));
        }
    }
}

你可能感兴趣的:(技巧,经典,线段树,矩阵乘法)