noip2019集训测试赛(四)A.fibonacci

Description

给定一个长度为 N 的序列 A={a1,a2,…,an} .

M 次操作, 每次操作形如下面两种中的一种:

1 l r x 将 a l , a l + 1 , . . . , a r a_l,a_{l+1},...,a_r al,al+1,...,ar 都加上 x ;

2 l r 求 ∑ i = l r f ( a i )   m o d   ( 1 0 9 + 7 ) \sum_{i=l}^rf(a_i)\ mod\ (10^9+7) i=lrf(ai) mod (109+7)

其中 f n f_n fn 为斐波那契数列的第 n 项, 即

{ f n = 1    n = 1 或 n = 2 f n = f n − 1 + f n − 2 n > 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. {fn=1  n=1n=2fn=fn1+fn2n>2


Input

第一行两个数 N,M .

第二行 N 个数 a1,a2,…,an .

接下来 M 行, 每行代表题目描述中的一种操作.


Output

对于每个询问, 输出一行, 表示答案.


Solution

这题是区间修改查询,一看就是线段树,但我们怎么处理序号加x呢?
众所周知,斐波那契数列可以用矩阵快速幂来搞。
所以,我们可以让线段树每一个节点储存一个表示 f ( a i ) f(a_i) f(ai)的矩阵,对于某个节点加x,只要用这个节点的矩阵乘上单位矩阵p的x次方。

那么问题来了,虽然可以处理单点,但区间怎么办?
其实对于一段区间,我们可以直接将矩阵每一位的值加起来再乘(即结合律),举个栗子:

[ f ( 2 ) f ( 3 ) 0 0 ] ∗ [ 0 1 1 1 ] + [ f ( 4 ) f ( 5 ) 0 0 ] ∗ [ 0 1 1 1 ] \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)0f(3)00111+f(4)0f(5)00111

= [ f ( 2 ) + f ( 4 ) f ( 3 ) + f ( 5 ) 0 0 ] ∗ [ 0 1 1 1 ] = \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(2)+f(4)0f(3)+f(5)00111

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

那么线段树统计区间直接将矩阵加起来就好啦。

因为常数较大,所以要适当卡常,而且应提前处理好每次加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)); 
    }
}

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
NOI(全国青少年信息学奥林匹克竞)模拟测试数据是指用于评测参选手的程序的输入和对应的输出。测试数据是非常重要的,因为它决定了参选手的程序能否正确地解决问题。 在NOI模拟中,测试数据具有以下特点: 1.充分覆盖:测试数据应涵盖各种可能的输入情况,包括边界条件和极端情况。通过提供不同的测试数据,可以考察选手对问题的全面理解和解决能力。 2.随机性和均衡性:为了公平起见,测试数据应该是随机生成的,而不是针对某个特定算法或解法设计的。同时,测试数据应该是均衡的,即各种情况的概率应该大致相等,以避免偏向某些解法。 3.合理性和可行性:测试数据应该是合理和可行的,即符合题目要求的输入数据,并且是选手能够通过编写程序来处理的。测试数据应该考虑到程序的限制和时间复杂度,以充分测试选手的编程能力。 NOI模拟测试数据通常由经验丰富的考题组负责生成。他们会根据题目的要求和限制,设计出一组合理、充分、随机和均衡的测试数据,以确保参选手的程序在各种情况下都能正确运行,并且能通过性能测试。 总之,测试数据在NOI模拟中起到了至关重要的作用,它既考察了选手对问题的理解和解决能力,又提高了选手编程的技巧和效率。同时,合理和恰当的测试数据也是公平竞的保证,确保每个参选手有相同的机会和条件进行竞争。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值