Codeforces 题目传送门 & 洛谷题目传送门

你可能会疑惑我为什么要写 *2400 的题的题解

首先一个很明显的想法是,看到斐波那契数列和 \(10^9+9\) 就想到通项公式,\(F_i=\dfrac{1}{\sqrt{5}}((\dfrac{1+\sqrt{5}}{2})^n-(\dfrac{1-\sqrt{5}}{2})^n)\)。并且 \(5\) 在模 \(10^9+9\) 意义下的二次剩余存在,为 \(383008016\)。

我们建两棵线段树分别维护展开式中 \((\dfrac{1+\sqrt{5}}{2})^n\) 和 \((\dfrac{1-\sqrt{5}}{2})^n\) 的部分,查询的时候原本的 \(a_i\) 可以做个前缀和 \(\mathcal O(1)\) 加上,其余部分直接在两棵线段树上区间查询相减并乘个 \(\dfrac{1}{\sqrt{5}}\) 即可。区间加操作相当于在两棵线段树区间 \([l,r]\) 加等比数列,这个可以用线段树区间加等比数列的套路维护。具体来说,我们以 \((\dfrac{1+\sqrt{5}}{2})^n\) 为例,线段树每个区间 \([L,R]\) 的懒标记 \(lz\) 表示该区间中第 \(i\in [L,R]\) 项的值要增加 \((\dfrac{1+\sqrt{5}}{2})^{i-L}\),然后你预处理 \(s_i=\sum\limits_{j=0}^{i-1}(\dfrac{1+\sqrt{5}}{2})^j\) 就可以 \(\mathcal O(1)\) 下放懒标记了。时间复杂度线对。

#include <bits/stdc++.h>
using namespace std;
#define fi first
#define se second
#define fill0(a) memset(a,0,sizeof(a))
#define fill1(a) memset(a,-1,sizeof(a))
#define fillbig(a) memset(a,63,sizeof(a))
#define pb push_back
#define ppb pop_back
#define mp make_pair
template<typename T1,typename T2> void chkmin(T1 &x,T2 y){if(x>y) x=y;}
template<typename T1,typename T2> void chkmax(T1 &x,T2 y){if(x<y) x=y;}
typedef pair<int,int> pii;
typedef long long ll;
typedef unsigned int u32;
typedef unsigned long long u64;
namespace fastio{
#define FILE_SIZE 1<<23
char rbuf[FILE_SIZE],*p1=rbuf,*p2=rbuf,wbuf[FILE_SIZE],*p3=wbuf;
inline char getc(){return p1==p2&&(p2=(p1=rbuf)+fread(rbuf,1,FILE_SIZE,stdin),p1==p2)?-1:*p1++;}
inline void putc(char x){(*p3++=x);}
template<typename T> void read(T &x){
x=0;char c=getchar();T neg=0;
while(!isdigit(c)) neg|=!(c^'-'),c=getchar();
while(isdigit(c)) x=(x<<3)+(x<<1)+(c^48),c=getchar();
if(neg) x=(~x)+1;
}
template<typename T> void recursive_print(T x){if(!x) return;recursive_print(x/10);putc(x%10^48);}
template<typename T> void print(T x){if(!x) putc('0');if(x<0) putc('-'),x=~x+1;recursive_print(x);}
void print_final(){fwrite(wbuf,1,p3-wbuf,stdout);}
}
const int SQRT_5=383008016;
const int INV2=5e8+5;
const int MAXN=3e5;
const int MOD=1e9+9;
int qpow(int x,int e){
int ret=1;
for(;e;e>>=1,x=1ll*x*x%MOD) if(e&1) ret=1ll*ret*x%MOD;
return ret;
}
int n,qu,INV_SQRT_5,a[MAXN+5],ss[MAXN+5];
struct segtree{
int base,pw[MAXN+5],sum[MAXN+5];
void prework(){
pw[0]=sum[0]=1;
for(int i=1;i<=n;i++) pw[i]=1ll*pw[i-1]*base%MOD;
for(int i=1;i<=n;i++) sum[i]=(sum[i-1]+pw[i])%MOD;
}
struct node{int l,r,sum,lz;} s[MAXN*4+5];
void build(int k,int l,int r){
s[k].l=l;s[k].r=r;if(l==r) return;
int mid=l+r>>1;build(k<<1,l,mid);build(k<<1|1,mid+1,r);
}
void pushup(int k){s[k].sum=(s[k<<1].sum+s[k<<1|1].sum)%MOD;}
void pushdown(int k){
if(s[k].lz){
s[k<<1].lz=(s[k<<1].lz+s[k].lz)%MOD;
s[k<<1].sum=(s[k<<1].sum+1ll*s[k].lz*sum[s[k<<1].r-s[k<<1].l])%MOD;
s[k<<1|1].lz=(s[k<<1|1].lz+1ll*s[k].lz*pw[s[k<<1].r-s[k<<1].l+1])%MOD;
s[k<<1|1].sum=(s[k<<1|1].sum+1ll*s[k].lz*sum[s[k<<1|1].r-s[k<<1|1].l]%MOD*pw[s[k<<1].r-s[k<<1].l+1])%MOD;
s[k].lz=0;
}
}
void modify(int k,int l,int r,int x){
if(l<=s[k].l&&s[k].r<=r){
s[k].sum=(s[k].sum+1ll*x*sum[s[k].r-s[k].l])%MOD;
s[k].lz=(s[k].lz+x)%MOD;return;
} pushdown(k);int mid=s[k].l+s[k].r>>1;
if(r<=mid) modify(k<<1,l,r,x);
else if(l>mid) modify(k<<1|1,l,r,x);
else modify(k<<1,l,mid,x),modify(k<<1|1,mid+1,r,1ll*x*pw[mid-l+1]%MOD);
pushup(k);
}
int query(int k,int l,int r){
if(l<=s[k].l&&s[k].r<=r) return s[k].sum;
pushdown(k);int mid=s[k].l+s[k].r>>1;
if(r<=mid) return query(k<<1,l,r);
else if(l>mid) return query(k<<1|1,l,r);
else return (query(k<<1,l,mid)+query(k<<1|1,mid+1,r))%MOD;
}
} s1,s2;
int main(){
INV_SQRT_5=qpow(SQRT_5,MOD-2);scanf("%d%d",&n,&qu);
for(int i=1;i<=n;i++) scanf("%d",&a[i]),ss[i]=(ss[i-1]+a[i])%MOD;
s1.base=1ll*(SQRT_5+1)*INV2%MOD;s2.base=1ll*(1-SQRT_5+MOD)*INV2%MOD;
s1.prework();s2.prework();s1.build(1,1,n);s2.build(1,1,n);
while(qu--){
int opt,l,r;scanf("%d%d%d",&opt,&l,&r);
if(opt==1) s1.modify(1,l,r,s1.base),s2.modify(1,l,r,s2.base);
else printf("%d\n",((ss[r]-ss[l-1]+MOD)%MOD+1ll*(s1.query(1,l,r)-s2.query(1,l,r)+MOD)*INV_SQRT_5%MOD)%MOD);
}
return 0;
}
/*
4 4
1 2 3 4
1 1 4
2 1 4
2 1 2
2 3 4
*/

当然我之所以写这个题解是因为还有别的做法。

上面的做法用到了模数是 \(10^9+9\) 的性质,倘若模数不是 \(10^9+9\) 那岂不就歇菜了?

考虑斐波那契数列的一个性质 \(F_n=F_{n-m}F_{m-1}+F_{n-m+1}F_m\)。

那么我们就有 \(F_{i-l+1}=F_iF_{-l}+F_{i+1}F_{-l+1}\)

这里我们定义负数下标的斐波那契数列为:\(F_{-1}=F_1-F_0=1,F_{-2}=F_0-F_{-1}=-1,F_{-3}=F_{-1}-F_{-2}=2\),以此类推。

显然 \(F_{-i}=(-1)^{i+1}F_i\)

至于为什么等式 \(F_n=F_{n-m}F_{m-1}+F_{n-m+1}F_m\) 对负数下标的斐波那契数列同样适用,可用跷跷板归纳法证明,这里就不再赘述了。

知道这个性质之后,考虑将代求的数列拆成两部分,\(a_i=F_ib_i+F_{i+1}c_i\)。那么一次区间修改操作相当于令 \([l,r]\) 中的 \(b_i\) 加上 \(F_{-l}\),\(c_i\) 加上 \(F_{-l+1}\)。于是问题转化为对于数列 \(A_i=B_iC_i\) 进行两种操作,将区间 \([l,r]\) 中的 \(C_i\) 加上 \(v\),求区间 \([l,r]\) 中所有 \(A_i\) 的和。这个可以用线段树维护,线段树上区间 \([L,R]\) 的懒标记 \(lz\) 表示 \([L,R]\) 中的 \(C_i\) 要加上 \(lz\)。考虑线段树每个区间记录一个 \(sum\) 表示 \(\sum\limits_{i=L}^RB_i\),这样可做到 \(\mathcal O(1)\) 下放懒标记,时间复杂度还是线对。

btw P5138 也是用的这个套路,既然这题写了题解那题就不写了罢

#include <bits/stdc++.h>
using namespace std;
#define fi first
#define se second
#define fill0(a) memset(a,0,sizeof(a))
#define fill1(a) memset(a,-1,sizeof(a))
#define fillbig(a) memset(a,63,sizeof(a))
#define pb push_back
#define ppb pop_back
#define mp make_pair
template<typename T1,typename T2> void chkmin(T1 &x,T2 y){if(x>y) x=y;}
template<typename T1,typename T2> void chkmax(T1 &x,T2 y){if(x<y) x=y;}
typedef pair<int,int> pii;
typedef long long ll;
typedef unsigned int u32;
typedef unsigned long long u64;
namespace fastio{
#define FILE_SIZE 1<<23
char rbuf[FILE_SIZE],*p1=rbuf,*p2=rbuf,wbuf[FILE_SIZE],*p3=wbuf;
inline char getc(){return p1==p2&&(p2=(p1=rbuf)+fread(rbuf,1,FILE_SIZE,stdin),p1==p2)?-1:*p1++;}
inline void putc(char x){(*p3++=x);}
template<typename T> void read(T &x){
x=0;char c=getchar();T neg=0;
while(!isdigit(c)) neg|=!(c^'-'),c=getchar();
while(isdigit(c)) x=(x<<3)+(x<<1)+(c^48),c=getchar();
if(neg) x=(~x)+1;
}
template<typename T> void recursive_print(T x){if(!x) return;recursive_print(x/10);putc(x%10^48);}
template<typename T> void print(T x){if(!x) putc('0');if(x<0) putc('-'),x=~x+1;recursive_print(x);}
void print_final(){fwrite(wbuf,1,p3-wbuf,stdout);}
}
const int MAXN=3e5;
const int MOD=1e9+9;
int n,qu,a[MAXN+5],fib[MAXN+5],fib_neg[MAXN+5],sum[MAXN+5];
struct node{int l,r,sum1,sum2,val1,val2,lz1,lz2;} s[MAXN*4+5];
void pushup(int k){
s[k].val1=(s[k<<1].val1+s[k<<1|1].val1)%MOD;
s[k].val2=(s[k<<1].val2+s[k<<1|1].val2)%MOD;
}
void pushdown(int k){
if(s[k].lz1||s[k].lz2){
s[k<<1].val1=(s[k<<1].val1+1ll*s[k<<1].sum1*s[k].lz1)%MOD;
s[k<<1].val2=(s[k<<1].val2+1ll*s[k<<1].sum2*s[k].lz2)%MOD;
s[k<<1].lz1=(s[k<<1].lz1+s[k].lz1)%MOD;
s[k<<1].lz2=(s[k<<1].lz2+s[k].lz2)%MOD;
s[k<<1|1].val1=(s[k<<1|1].val1+1ll*s[k<<1|1].sum1*s[k].lz1)%MOD;
s[k<<1|1].val2=(s[k<<1|1].val2+1ll*s[k<<1|1].sum2*s[k].lz2)%MOD;
s[k<<1|1].lz1=(s[k<<1|1].lz1+s[k].lz1)%MOD;
s[k<<1|1].lz2=(s[k<<1|1].lz2+s[k].lz2)%MOD;
s[k].lz1=s[k].lz2=0;
}
}
void build(int k,int l,int r){
s[k].l=l;s[k].r=r;if(l==r){s[k].sum1=fib[l];s[k].sum2=fib[l+1];return;}
int mid=l+r>>1;build(k<<1,l,mid);build(k<<1|1,mid+1,r);
s[k].sum1=(s[k<<1].sum1+s[k<<1|1].sum1)%MOD;
s[k].sum2=(s[k<<1].sum2+s[k<<1|1].sum2)%MOD;
}
void modify(int k,int l,int r,int v1,int v2){
if(l<=s[k].l&&s[k].r<=r){
s[k].lz1=(s[k].lz1+v1)%MOD;s[k].lz2=(s[k].lz2+v2)%MOD;
s[k].val1=(s[k].val1+1ll*s[k].sum1*v1%MOD)%MOD;
s[k].val2=(s[k].val2+1ll*s[k].sum2*v2%MOD)%MOD;
return;
} pushdown(k);int mid=s[k].l+s[k].r>>1;
if(r<=mid) modify(k<<1,l,r,v1,v2);
else if(l>mid) modify(k<<1|1,l,r,v1,v2);
else modify(k<<1,l,mid,v1,v2),modify(k<<1|1,mid+1,r,v1,v2);
pushup(k);
}
int query(int k,int l,int r){
if(l<=s[k].l&&s[k].r<=r) return (s[k].val1+s[k].val2)%MOD;
pushdown(k);int mid=s[k].l+s[k].r>>1;
if(r<=mid) return query(k<<1,l,r);
else if(l>mid) return query(k<<1|1,l,r);
else return (query(k<<1,l,mid)+query(k<<1|1,mid+1,r))%MOD;
}
int main(){
scanf("%d%d",&n,&qu);
for(int i=1;i<=n;i++) scanf("%d",&a[i]),sum[i]=(sum[i-1]+a[i])%MOD;
fib[1]=fib[2]=1;fib_neg[1]=1;fib_neg[2]=MOD-1;
for(int i=3;i<=n+1;i++){
fib[i]=(fib[i-1]+fib[i-2])%MOD;
if(~i&1) fib_neg[i]=MOD-fib[i];
else fib_neg[i]=fib[i];
} build(1,1,n);
while(qu--){
int opt,l,r;scanf("%d%d%d",&opt,&l,&r);
if(opt==1) modify(1,l,r,fib_neg[l],fib_neg[l-1]);
else printf("%d\n",((sum[r]-sum[l-1]+MOD)%MOD+query(1,l,r))%MOD);
}
return 0;
}

当然此题还有可能有别的方法,不过由于我太懒了就不写了/wq

Codeforces 446C - DZY Loves Fibonacci Numbers(斐波那契数列+线段树)的更多相关文章

  1. Codeforces 446-C DZY Loves Fibonacci Numbers 同余 线段树 斐波那契数列

    C. DZY Loves Fibonacci Numbers time limit per test 4 seconds memory limit per test 256 megabytes inp ...

  2. codeforces 446C DZY Loves Fibonacci Numbers(数学 or 数论+线段树)(两种方法)

    In mathematical terms, the sequence Fn of Fibonacci numbers is defined by the recurrence relation F1 ...

  3. codeforces 446C DZY Loves Fibonacci Numbers 数论+线段树成段更新

    DZY Loves Fibonacci Numbers Time Limit:4000MS     Memory Limit:262144KB     64bit IO Format:%I64d &a ...

  4. Codeforces 446C —— DZY Loves Fibonacci Numbers(线段树)

    题目:DZY Loves Fibonacci Numbers 题意比較简单,不解释了. 尽管官方的题解也是用线段树,但还利用了二次剩余. 可是我没有想到二次剩余,然后写了个感觉非常复杂度的线段树,还是 ...

  5. ACM学习历程—Codeforces 446C DZY Loves Fibonacci Numbers(线段树 && 数论)

    Description In mathematical terms, the sequence Fn of Fibonacci numbers is defined by the recurrence ...

  6. codeforces 446C DZY Loves Fibonacci Numbers 线段树

    假如F[1] = a, F[2] = B, F[n] = F[n - 1] + F[n - 2]. 写成矩阵表示形式可以很快发现F[n] = f[n - 1] * b + f[n - 2] * a. ...

  7. [Amazon] Program for Fibonacci numbers 斐波那契数列

    The Fibonacci numbers are the numbers in the following integer sequence. 0, 1, 1, 2, 3, 5, 8, 13, 21 ...

  8. Codeforces 446C DZY Loves Fibonacci Numbers [线段树,数论]

    洛谷 Codeforces 思路 这题知道结论就是水题,不知道就是神仙题-- 斐波那契数有这样一个性质:\(f_{n+m}=f_{n+1}f_m+f_{n}f_{m-1}\). 至于怎么证明嘛-- 即 ...

  9. 10、end关键字和Fibonacci series: 斐波纳契数列

    # Fibonacci series: 斐波纳契数列 # 两个元素的总和确定了下一个数 a, b = 0, 1 #复合赋值表达式,a,b同时赋值0和1 while b < 10: print(b ...

随机推荐

  1. 从零开始的Spring Session(一)

    Session和Cookie这两个概念,在学习java web开发之初,大多数人就已经接触过了.最近在研究跨域单点登录的实现时,发现对于Session和Cookie的了解,并不是很深入,所以打算写两篇 ...

  2. Java并发:重入锁 ReentrantLock(一)

    ReentrantLock 是一种可重入的互斥锁,它不像 synchronized关键字一样支持隐式的重进入,但能够使一个线程(不同的方法)重复对资源的重复加锁而不受阻塞. ReentrantLock ...

  3. std::string类详解

    之所以抛弃char*的字符串而选用C++标准程序库中的string类,是因为他和前者比较起来,不必 担心内存是否足够.字符串长度等等,而且作为一个类出现,他集成的操作函数足以完成我们大多数情况下(甚至 ...

  4. DeWeb 电脑和手机动态适配

    DeWeb 做多平台适配很方便! 多平台适配代码在OnMouseUp中. X,Y分别表示当前设备的Width/Height: Button : mbLeft : 屏幕纵向, mbRight:屏幕横向: ...

  5. java 垃圾回收及内存分配策略

    一.在垃圾收集器对堆进行回收前,首先需要判断对象是否"存活",对已经"死去"的对象进行回收 判断对象是否存活:引用计数法和可达性分析法 引用计数法:给对象添加一 ...

  6. centos7 永久修改hostname

    1.修改 /etc/sysconfig/network ,重启后还是原来的主机名 2.百度有说明是/etc/rc.d/rc.sysinit下的脚本还原了还来主机名,问题是的我主机上没有这个脚本 3.执 ...

  7. c++ template 实现一个简单的"栈"

    一: 实现一个简单的swap 原来我们写swap一定会这样写: 对于int类型的: swap(const int &x,const int &y) { int temp; temp = ...

  8. Jackson & fastJson的使用

    Jackson import lombok.Data; @Data public class Student { private Long id; private String name; priva ...

  9. vue的逆向传值(子传父)

    逆向传值:子组件传值给父组件叫做逆向传值  (是不v欸允许的,必须经过事件触发才能传值) 逆向传值步骤: 1.要传值必须先抛出,在接收 语法: this.$emit("event" ...

  10. KMP算法,看这篇就够了!

    普通的模式匹配算法(BF算法) 子串的定位操作通常称为模式匹配算法 假设有一个需求,需要我们从串"a b a b c a b c a c b a b"中,寻找内容为"a ...