noip2019集訓測試賽(四)A.fibonacci

Description

給定一個長度爲 N 的序列 A={a1,a2,…,an} .

M 次操作, 每次操作形如下面兩種中的一種:

1 l r x 將 al,al+1,...,ara_l,a_{l+1},...,a_r 都加上 x ;

2 l r 求i=lrf(ai) mod (109+7)\sum_{i=l}^rf(a_i)\ mod\ (10^9+7)

其中 fnf_n 爲斐波那契數列的第 n 項, 即

{fn=1  n=1n=2fn=fn1+fn2n>2 \left \{ \begin{array}{ll} f_n=1\qquad\qquad\qquad \ \ n=1或n=2\\ f_n=f_{n-1}+f_{n-2}\qquad n>2 \end{array} \right.


Input

第一行兩個數 N,M .

第二行 N 個數 a1,a2,…,an .

接下來 M 行, 每行代表題目描述中的一種操作.


Output

對於每個詢問, 輸出一行, 表示答案.


Solution

這題是區間修改查詢,一看就是線段樹,但我們怎麼處理序號加x呢?
衆所周知,斐波那契數列可以用矩陣快速冪來搞。
所以,我們可以讓線段樹每一個節點儲存一個表示f(ai)f(a_i)的矩陣,對於某個節點加x,只要用這個節點的矩陣乘上單位矩陣p的x次方。

那麼問題來了,雖然可以處理單點,但區間怎麼辦?
其實對於一段區間,我們可以直接將矩陣每一位的值加起來再乘(即結合律),舉個栗子:

[f(2)f(3)00][0111]+[f(4)f(5)00][0111]\quad\begin{bmatrix} f(2)&f(3) \\\\ 0&0 \end{bmatrix} \quad * \quad \begin{bmatrix} 0&1 \\\\ 1&1 \end{bmatrix} \quad+\quad\begin{bmatrix} f(4)&f(5) \\\\ 0&0 \end{bmatrix} \quad*\quad\begin{bmatrix} 0&1 \\\\ 1&1 \end{bmatrix}\quad

=[f(2)+f(4)f(3)+f(5)00][0111]= \begin{bmatrix} f(2)+f(4)&f(3)+f(5) \\\\ 0&0 \end{bmatrix}\quad*\quad\begin{bmatrix} 0&1 \\\\ 1&1 \end{bmatrix}

=[f(3)+f(5)f(4)+f(6)00]= \begin{bmatrix} f(3)+f(5)&f(4)+f(6 ) \\\\ 0&0 \end{bmatrix}

那麼線段樹統計區間直接將矩陣加起來就好啦。

因爲常數較大,所以要適當卡常,而且應提前處理好每次加x乘的矩陣,保證複雜度。
但是似乎出題人預處理了2的冪,所以跑得比我快。


Code

#include<bits/stdc++.h>
using namespace std;
const long long mod=1e9+7;
long long s[400010];
bool islaz[400010];
struct node{
    long long a[3][3];
    node(){
        memset(a,0,sizeof(a));
    }
}pp,p,p1,tree[400010],laz[400010];
node func(node a,node b){
    node c;
    for(int i=1;i<=2;i++)
    for(int j=1;j<=2;j++)
    for(int k=1;k<=2;k++)
    c.a[i][j]=(c.a[i][j]+a.a[i][k]*b.a[k][j])%mod;
    return c;
}
void build(int id,int l,int r){
    if(l==r){
        p=pp;
        laz[id]=node();
        int x=s[l]-1;
        tree[id].a[1][2]=1;
        while(x>0){
            if(x%2==1) tree[id]=func(tree[id],p);
            p=func(p,p);
            x/=2;
        }
        return;
    }
    int mid=(l+r)>>1;
    build(id*2,l,mid);
    build(id*2+1,mid+1,r);
    for(int i=1;i<=2;i++)
    for(int j=1;j<=2;j++)
    tree[id].a[i][j]=(tree[id*2].a[i][j]+tree[id*2+1].a[i][j])%mod;
}
void pushdown(int id){
    if(islaz[id]){
        islaz[id]=0;
        tree[id*2]=func(tree[id*2],laz[id]);
        tree[id*2+1]=func(tree[id*2+1],laz[id]);
        if(islaz[id*2]) laz[id*2]=func(laz[id*2],laz[id]);
        else laz[id*2]=laz[id],islaz[id*2]=1;
        if(islaz[id*2+1]) laz[id*2+1]=func(laz[id*2+1],laz[id]);
        else laz[id*2+1]=laz[id],islaz[id*2+1]=1;
        laz[id]=node();
    } 
}
void update(int id,int l,int r,int ul,int ur){
    if(ul<=l&&r<=ur){
        tree[id]=func(tree[id],p1);
        if(islaz[id]) laz[id]=func(laz[id],p1);
        else laz[id]=p1,islaz[id]=1;
        return;
    }
    pushdown(id);
    int mid=(l+r)>>1;
    if(ul<=mid) update(id*2,l,mid,ul,ur);
    if(ur>mid) update(id*2+1,mid+1,r,ul,ur);
    for(int i=1;i<=2;i++)
    for(int j=1;j<=2;j++)
    tree[id].a[i][j]=(tree[id*2].a[i][j]+tree[id*2+1].a[i][j])%mod;
}
int query(int id,int l,int r,int ql,int qr){
    if(ql<=l&&r<=qr) 
    return tree[id].a[1][2]%mod; 
    pushdown(id);
    int mid=(l+r)>>1,sum=0;
    if(ql<=mid) sum=(sum+0ll+query(id*2,l,mid,ql,qr)%mod)%mod;
    if(qr>mid) sum=(sum+0ll+query(id*2+1,mid+1,r,ql,qr)%mod)%mod;
    return sum; 
}
inline int read(){
    char c=getchar(); long long x=0,f=1;
    while(!isdigit(c) && c!='-') c=getchar();
    if(c=='-') { f=-1; c=getchar(); }
    while(isdigit(c)) { x=(x<<3)+(x<<1)+c-'0'; c=getchar(); }
    return x*f;
}
int main(){
    int n,m,op,l,r,x;
    n=read(),m=read();
    for(int i=1;i<=n;i++)
    s[i]=read();
    pp.a[1][2]=pp.a[2][1]=pp.a[2][2]=1;
    build(1,1,n);
    for(int i=1;i<=m;i++){
        op=read(),l=read(),r=read();
        if(op==1){
            x=read();
            p1=pp;  p=pp;
            int x1=x-1;
            while(x1>0){
                if(x1%2==1) p1=func(p1,p);
                p=func(p,p);
                x1/=2;
            }
            update(1,1,n,l,r);
        }
        else printf("%d\n",query(1,1,n,l,r)); 
    }
}

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章