CF446C. DZY Loves Fibonacci Numbers
好牛的题,写一下。
题意:维护一个序列 \(a\),长度为 \(n\),有 \(m\) 次操作:
-
1 l r
:对于 \(i\in[l,r]\),\(a_i\leftarrow a_i+f_{i-l+1}\)。 -
2 l r
:求 \(\displaystyle\left(\sum_{i=l}^ra_i\right)\bmod(10^9+9)\)。
其中 \(f_{i}\) 表示第 \(i\) 个斐波那契数(\(f_0=0,f_1=1,f_n=f_{n-1}+f_{n-2}(n\ge2)\))。
\(1\le n,m\le3\times10^5\),\(1\le a_i\le10^9\)。
读完题后,我们不难猜到这个题应该要用线段树来维护。那怎么做到区间加斐波那契数列呢?
我们发现,如果能求出 \(f_n\) 的通项公式,区间加的操作就是可行的了。
记斐波那契数列 \(f\) 的普通生成函数为 \(F(x)\),根据定义式我们有 \(F(x)=\sum_{n\ge0}f_nx^n\)。变形得:
整理得 \((1-x-x^2)F(x)=x\),解得 \(F(x)=\dfrac{x}{1-x-x^2}\)。
我们已经推出了 \(F(x)\) 的一种形式,接下来要把它展开了。
考虑两个序列 \(a,b\) 的普通生成函数,分别记为为 \(F_a(x),F_b(x)\)。那么有 \(F_a(x)\pm F_b(x)=\sum_{n\ge0}(a_n\pm b_n)x^n\)。因此 \(F_a(x)\pm F_b(x)\) 是序列 \(\langle a_n\pm b_n\rangle\) 的普通生成函数。这个我们待会要用。
再考虑等比数列 \(\langle 1,d,d^2\ldots\rangle\) 的普通生成函数,记为 \(G(x)\)。那么有:
整理得 \((1-dx)G(x)=1\),解得 \(G(x)=\dfrac{1}{1-dx}\)。这个我们待会也要用。
令:
解得:
即:
联系上文推出的等比数列的展开式的形式,以及普通生成函数的基本运算知识,可以得到 \(F(x)\) 的另一种形式:
即斐波那契数列 \(f_n\) 的通项公式为:
这个东西可以左右两半拆开,把 \(\dfrac{1}{\sqrt{5}}\) 提出来,两边分别用区间加等比数列维护。
虽然我们已经推出了 \(f_n\) 的通项公式,但式子中包含无理数,怎么处理呢?
定义 \(n\) 在膜 \(p\) 意义下的二次剩余为满足 \(x^2\equiv n\pmod p\) 的整数 \(x\)。那么我们只需要求出无理数的二次剩余就可以维护了。
这里,我们并不需要去学习如何求二次剩余。因为我们只需要知道 \(\dfrac{1}{\sqrt{5}}\) 这一个数的二次剩余,我们可以枚举一下,得到 \(383008016^2\equiv5\pmod{10^9+9}\)。其余求逆元等过程按下不表。
由于这个问题的特殊性,我们只需要维护区间加首项为 \(1\),公比固定的等比数列。区间维护 \(tag\) 表示当前区间加的等比数列的首项,显然两次操作可以合并。
code:
点击查看代码
#include<bits/stdc++.h>
#define int long long
using namespace std;
const int inf=1e18,mod=1e9+9;
const int A=276601605;
const int D[2]={691504013,308495997};
inline int read(){
int x=0,f=1;char ch=getchar();
while (!isdigit(ch)){if (ch=='-') f=-1;ch=getchar();}
while (isdigit(ch)){x=x*10+ch-48;ch=getchar();}
return x*f;
}
int pw[2][300005],spw[2][300005];
int ask(int o,int l,int r){
if(l==0)return spw[o][r];
return (spw[o][r]-spw[o][l-1]+mod)%mod;
}
struct segtree{
#define ls p<<1
#define rs p<<1|1
#define lson l,mid,ls
#define rson mid+1,r,rs
struct Node{
int s[2],tag[2];
}c[1200005];
void pushup(int p){
for(int o=0;o<2;o++)c[p].s[o]=(c[ls].s[o]+c[rs].s[o])%mod;
}
void pushdown(int l,int r,int p){
int mid=(l+r)>>1;
for(int o=0;o<2;o++){
c[ls].s[o]=(c[ls].s[o]+c[p].tag[o]*ask(o,0,mid-l)%mod)%mod,c[rs].s[o]=(c[rs].s[o]+c[p].tag[o]*ask(o,mid+1-l,r-l)%mod)%mod;
c[ls].tag[o]=(c[ls].tag[o]+c[p].tag[o])%mod,c[rs].tag[o]=(c[rs].tag[o]+c[p].tag[o]*pw[o][mid-l+1]%mod)%mod;
c[p].tag[o]=0;
}
}
void build(int l,int r,int p){
for(int o=0;o<2;o++)c[p].tag[o]=0;
if(l==r){
for(int o=0;o<2;o++)c[p].s[o]=0;
return;
}
int mid=(l+r)>>1;
build(lson),build(rson);
pushup(p);
}
void update(int l,int r,int p,int L,int R){
if(L<=l&&r<=R){
for(int o=0;o<2;o++){
c[p].s[o]=(c[p].s[o]+ask(o,l-L+1,r-L+1))%mod;
c[p].tag[o]=(c[p].tag[o]+pw[o][l-L+1])%mod;
}
return;
}
int mid=(l+r)>>1;pushdown(l,r,p);
if(L<=mid)update(lson,L,R);
if(R>mid)update(rson,L,R);
pushup(p);
}
int query(int l,int r,int p,int L,int R){
if(L<=l&&r<=R)return (c[p].s[0]-c[p].s[1]+mod)%mod;
int mid=(l+r)>>1,res=0;pushdown(l,r,p);
if(L<=mid)res=(res+query(lson,L,R))%mod;
if(R>mid)res=(res+query(rson,L,R))%mod;
return res;
}
#undef ls
#undef rs
#undef lson
#undef rson
}Tr;
int a[300005],s[300005];
signed main(){
int n=read(),m=read();
for(int o=0;o<2;o++){
pw[o][0]=spw[o][0]=1;
for(int i=1;i<=n;i++)pw[o][i]=pw[o][i-1]*D[o]%mod,spw[o][i]=(spw[o][i-1]+pw[o][i])%mod;
}
for(int i=1;i<=n;i++)a[i]=read(),s[i]=(s[i-1]+a[i])%mod;
Tr.build(1,n,1);
while(m--){
int o=read(),l=read(),r=read();
if(o==1)Tr.update(1,n,1,l,r);
else printf("%lld\n",((s[r]-s[l-1]+mod)%mod+A*Tr.query(1,n,1,l,r)%mod)%mod);
}
return 0;
}