提高培优班D2T4线段树题解

提高培优班D2T4线段树题解

题目id:4306

题意简述

给定一个序列 a ,维护区间加,区间求 \Sigma_{i=1}^{n}f(a_i)\ mod\ (1\times10^9+7)

f(a_i) 表示第 a_i斐波那契数列的值,特别的,$f(1)=1\ ,\ f(2)=1$ 。

n,m\le1\times10^5 ,保证输入为 \le1\times10^9 的正整数。

思路

看到斐波那契数列,那肯定会想到其通项计算公式和矩阵快速幂计算。

因为矩阵快速幂常数大,所以我是先试试通项计算公式的,然后忘了通向是啥了,就只能现推。

对于 f_n=f_{n-1}+f_{n-2} ,其形式接近等比数列 ,于是我们假设 f_n=A*q^{n}

可以得到:$Aq^n=Aq^{n-1}+A*q^{n-2}$

移项得:$q^2-q-1=0$

\therefore q=\frac{1\pm\sqrt{5}}{2}

\therefore f_n=A*(\frac{1+\sqrt{5}}{2})^n+B*(\frac{1-\sqrt{5}}{2})^n

代入 f_1\ ,\ f_2 得:$f_n=\frac{(\frac{1+\sqrt5}{2})^n-(\frac{1-\sqrt5}{2})^n}{\sqrt{5}}$

发现,我们并不能对它做跟区间有关的操作。推了个寂寞

那么,就只剩下,矩阵快速幂了,我们令计算矩阵为 g 。

注意到,对于 f_{a_i} \rightarrow f_{a_i+x} 的转移,其实就是 f_{a_i} 所对应的矩阵 \times g^x

那么问题就很简单了,所谓的区间加,其实是区间乘,然后维护区间和即可。

显然,线段树、分块等都可以写,主要是一些细节问题,这里主要讲线段树,我才不会说我分块写炸了

首先, a_i 直接变成所对应的矩阵,因为矩阵满足分配律和结合律,所以上推的时候直接进行矩阵加法即可。

然后对于区间加 x ,直接把它变成 g^x ,然后做区间乘法。

最后,你可以试试重载运算符,这样相当于,打了个矩阵模板,然后打了个线段树模板,就过了。

时间复杂度 \Theta(n\log n)

code

#define lp (p<<1)
#define rp (p<<1|1)
int n,m,a[N];
class node{
	public:
	int g[2][2];
	inline void re(){
		g[0][0]=g[0][1]=g[1][0]=1,g[1][1]=0;//计算矩阵 
	}
	inline void rr(){
		g[0][0]=g[1][1]=1;g[0][1]=g[1][0]=0;//单位矩阵 
	}
	friend node operator *(node x,node y);
	friend node operator +(node x,node y);
};
node operator *(node x,node y){
	node z;
	z.g[0][0]=(1ll*x.g[0][0]*y.g[0][0]%mod+1ll*x.g[0][1]*y.g[1][0]%mod)%mod;z.g[0][1]=(1ll*x.g[0][0]*y.g[0][1]%mod+1ll*x.g[0][1]*y.g[1][1]%mod)%mod;
	z.g[1][0]=(1ll*x.g[1][0]*y.g[0][0]%mod+1ll*x.g[1][1]*y.g[1][0]%mod)%mod;z.g[1][1]=(1ll*x.g[1][0]*y.g[0][1]%mod+1ll*x.g[1][1]*y.g[1][1]%mod)%mod;
	return z;
}
node operator +(node x,node y){
	node z;
	z.g[0][0]=(x.g[0][0]+y.g[0][0])%mod;z.g[0][1]=(x.g[0][1]+y.g[0][1])%mod;
	z.g[1][0]=(x.g[1][0]+y.g[1][0])%mod;z.g[1][1]=(x.g[1][1]+y.g[1][1])%mod;
	return z;
}
inline node qpow(int y){
	node A,B;A.rr();B.re();
	for(;y;y>>=1,B=B*B) if(y&1) A=A*B; 
	return A;
}
struct segmentree{node v;node mul;}e[N<<2];
void build(int p,int l,int r){
	e[p].mul.rr();
	if(l==r)
		e[p].v=qpow(a[l]-1);
	else {
		int mid=l+r>>1;build(lp,l,mid);build(rp,mid+1,r);
		e[p].v=(e[lp].v+e[rp].v);
	}
}
void spread(int p){
	e[lp].v=(e[lp].v*e[p].mul);
	e[rp].v=(e[rp].v*e[p].mul);
	e[lp].mul=(e[lp].mul*e[p].mul);
	e[rp].mul=(e[rp].mul*e[p].mul);
	e[p].mul.rr();
}
void upd(int p,int l,int r,int L,int R,int k){
//	printf("p:%d l:%d r:%d val:%d mul: %d\n",p,l,r,e[p].v.g[0][1],e[p].mul);
	if(l>R||r<L)return ;
	if(L<=l&&R>=r){
		e[p].v=e[p].v*qpow(k);
		e[p].mul=(e[p].mul*qpow(k));
		return ;
	}
	spread(p);
	int mid=l+r>>1;
	if(mid>=L)upd(lp,l,mid,L,R,k);
	if(mid<R)upd(rp,mid+1,r,L,R,k);
	e[p].v=(e[lp].v+e[rp].v);
	return;
}
int ask(int p,int l,int r,int L,int R){
//	printf("p:%d l:%d r:%d val:%d mul: %d\n",p,l,r,e[p].v.g[0][1],e[p].mul);
	if(L<=l&&R>=r){
//		printf("<p:%d l:%d r:%d val:%d mul: %d>\n",p,l,r,e[p].v.g[0][1],e[p].mul);
		return e[p].v.g[0][0];
	}
	spread(p);
	int ret=0;
	int mid=l+r>>1;
	if(mid>=L)ret=(ret+ask(lp,l,mid,L,R))%mod;
	if(mid<R)ret=(ret+ask(rp,mid+1,r,L,R))%mod;
	//printf("[%d %d] : %d right : %d\n", l, r, ret, ask(rp, mid + 1, r, L, R));
	return ret;
}
signed main(){
	read(n,m);
	for(int i=1;i<=n;i++) read(a[i]);
	build(1,1,n);
	int opt,l,r,x;
	while(m--){
		read(opt,l,r);
		if(opt==1) read(x),upd(1,1,n,l,r,x);
		else printf("%lld\n",ask(1,1,n,l,r));
	}
	return 0;
}
1 个赞

复杂度错了。

你这是 $\mathcal{O}(q\log n \log |V|)$。

原因是在线段树内调用快速幂。你应该先把转移矩阵处理好,再在线段树上 \mathcal{O}(1) 矩阵乘法

1 个赞

不过大抵是都能过的,建议把复杂度改一下

2 个赞