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. JavaCPP快速入门(官方demo增强版)

    欢迎访问我的GitHub https://github.com/zq2599/blog_demos 内容:所有原创文章分类汇总及配套源码,涉及Java.Docker.Kubernetes.DevOPS ...

  2. 【UE4】GAMES101 图形学作业2:光栅化和深度缓存

    总览 在上次作业中,虽然我们在屏幕上画出一个线框三角形,但这看起来并不是那么的有趣.所以这一次我们继续推进一步--在屏幕上画出一个实心三角形,换言之,栅格化一个三角形.上一次作业中,在视口变化之后,我 ...

  3. 【UE4 调试】C++ 常见编译 warnnings/errors

    error LNK2019: unresolved external symbol "" referenced in function 描述 Link错误.无法解析的外部符号 解决 ...

  4. Java:重载和重写

    Java:重载和重写 对 Java 中的 重载和重写 这个概念,做一个微不足道的小小小小结 重载 重载:编译时多态,同一个类中的同名的方法,参数列表不同,与返回值无关. 有以下几点: 方法名必须相同: ...

  5. Codeforces Round #573 (Div. 2) D题题解

    一.题目 ​ Tokitsukaze, CSL and Stone Game ​ Tokitsukaze和CSL正在玩一些石头游戏. ​ 一开始,有n堆的石头,第i堆石头数记为 \(a_i\),两人轮 ...

  6. [no_code团队]项目介绍 & 需求分析 & 发布预测

    项目 内容 2020春季计算机学院软件工程(罗杰 任健) 博客园班级博客 作业要求 团队项目选择 我们在这个课程的目标是 在团队合作中提升软件开发水平 这个作业在哪个具体方面帮助我们实现目标 进行项目 ...

  7. 2021.10.15考试总结[NOIP模拟77]

    \(n=40\)考虑\(meet \;in \;the \;middle\) 某个元素有关的量只有一个时考虑转化为树上问题 对暴力有自信,相信数据有梯度 没了 UPD:写了个略说人话的. T1 最大或 ...

  8. 「总结」$dp1$

    大概就是做点题. 先列一下要做的题目列表,从\(UOJ\)上找的. 129寿司晚宴 348州区划分 370滑稽树上滑稽果 457数树 22外星人 37主旋律 300吉夫特 196线段树 311积劳成疾 ...

  9. Linux多线程编程之详细分析

    线程?为什么有了进程还需要线程呢,他们有什么区别?使用线程有什么优势呢?还有多线程编程的一些细节问题,如线程之间怎样同步.互斥,这些东西将在本文中介绍.我见到这样一道面试题: 是否熟悉POSIX多线程 ...

  10. STL模板

    目录 栈stack 队列queue 列表List 集合set 映射map 多重映射multimap 对pair 元组tuple 容器containers 算法algorithms 仿函数/函数对象fu ...