CF446C. DZY Loves Fibonacci Numbers

发布时间 2023-05-20 16:00:04作者: xx019

好牛的题,写一下。

题意:维护一个序列 \(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\)。变形得:

\[\begin{align} F(x)&=\sum_{n\ge0}f_nx^n\\ &=f_0x^0+f_1x^1+\sum_{n\ge2}f_nx^n\\ &=0+x+\sum_{n\ge2}(f_{n-1}+f_{n-2})x^n\\ &=0+x+x\sum_{n\ge1}f_{n}x^n+x^2\sum_{n\ge0}f_nx^n\\ &=0+x+x(F(x)-f_0x^0)+x^2F(x)\\ \end{align} \]

整理得 \((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)\)。那么有:

\[\begin{align} G(x)&=\sum_{n\ge0}d^nx^n\\ &=1+\sum_{n\ge1}d^nx^n\\ &=1+dx\sum_{n\ge0}d^nx^n\\ &=1+dxG(x) \end{align} \]

整理得 \((1-dx)G(x)=1\),解得 \(G(x)=\dfrac{1}{1-dx}\)。这个我们待会也要用。

令:

\[\dfrac{A}{1-ax}+\dfrac{B}{1-bx}=\dfrac{x}{1-x-x^2} \]

解得:

\[\begin{cases} A=\frac{1}{\sqrt{5}}\\ B=-\frac{1}{\sqrt{5}}\\ a=\frac{1+\sqrt{5}}{2}\\ b=\frac{1-\sqrt{5}}{2} \end{cases} \]

即:

\[\frac{1}{\sqrt{5}}\displaystyle\left(\dfrac{1}{1-\frac{1+\sqrt{5}}{2}x}-\dfrac{1}{1-\frac{1-\sqrt{5}}{2}x}\right)=\dfrac{x}{1-x-x^2} \]

联系上文推出的等比数列的展开式的形式,以及普通生成函数的基本运算知识,可以得到 \(F(x)\) 的另一种形式:

\[F(x)=\frac{x}{1-x-x^2}=\sum_{n\ge0}x^n\frac{1}{\sqrt{5}}\left(\left(\frac{1+\sqrt{5}}{2}\right)^n-\left(\frac{1-\sqrt{5}}{2}\right)^n\right) \]

即斐波那契数列 \(f_n\) 的通项公式为:

\[f_n=\frac{1}{\sqrt{5}}\left(\left(\frac{1+\sqrt{5}}{2}\right)^n-\left(\frac{1-\sqrt{5}}{2}\right)^n\right) \]

这个东西可以左右两半拆开,把 \(\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;
}