[nowcoder 2020] 斐波

该博客详细介绍了如何使用矩阵快速幂和线段树解决大数范围内斐波那契数平方和的计算问题。首先,通过矩阵变换优化计算fib2(i),然后利用子集枚举的转移矩阵相乘特性处理子集贡献,最后结合区间计算和单点修改,应用线段树进行高效求解。文章提供了完整的C++代码实现,适合对算法和数据结构有一定基础的读者学习研究。
摘要由CSDN通过智能技术生成

一、题目

点此看题

二、解法

面对这种题的时候,不要急,急了反而没有。真正的方法是一步一步来,你会发现都是套路。

解决本题需要三步走,这是由中国的国情决定的

0x01

怎么计算 f i b 2 ( i ) fib^2(i) fib2(i) ,由于 i i i很大( 1 e 10 1e10 1e10范围),除了矩阵加速我们别无选择,紧紧抓住我们唯一拥有的条件: f i b ( i ) = f i b ( i − 1 ) + f i b ( i − 2 ) fib(i)=fib(i-1)+fib(i-2) fib(i)=fib(i1)+fib(i2)那么 f i b 2 ( i ) = f i b 2 ( i − 1 ) + f i b 2 ( i − 2 ) + 2 f i b ( i − 1 ) f i b ( i − 2 ) fib^2(i)=fib^2(i-1)+fib^2(i-2)+2fib(i-1)fib(i-2) fib2(i)=fib2(i1)+fib2(i2)+2fib(i1)fib(i2)那么维护三个变元: f i b 2 ( i ) , f i b 2 ( i − 1 ) , f i b ( i ) f i b ( i − 1 ) fib^2(i),fib^2(i-1),fib(i)fib(i-1) fib2(i),fib2(i1),fib(i)fib(i1),矩阵不难写出吧:
1 1 2 0 1 0 0 0 1 \begin{matrix}1&1&2\\0&1&0\\0&0&1\end{matrix} 100110201分析得到我们要的是 i i i次方后维护第二行第一列的值。

0x02

这个问题是如果解决子集枚举的问题,好像这个部分我见到的套路着实不多,其中有一种是算贡献(这里用不到),或者是利用一些特殊性质。 f i b 2 ( S ) fib^2(S) fib2(S)的计算其实就是属于里面元素的转移矩阵相乘,支持乘法的性质,有趣。我们加入单位矩阵来表示选与不选:
( I + A s 1 ) ( I + A s 2 ) . . . . . . (I+A^{s_1})(I+A^{s_2})...... (I+As1)(I+As2)......其中 I I I表示单位矩阵, A A A表示转移矩阵,那么这一部分就完成了!

0x03

他吗的,外面还套了一个区间计算,还有单点修改,很明显了,考虑下线段树。

关键在于线段树两个儿子的合并,类似于 c d q cdq cdq,单独选左边儿子和右边儿子的贡献我们已经知道了,现在要算交叉贡献:
在这里插入图片描述

上面画的线代表前缀 / / /后缀的乘积,交叉贡献是任取一条红线和蓝线乘起来算贡献,那么维护左边蓝线的和,右边红线的和,直接将他们相乘就可以了,我们得到了维护答案的方法。

那么红线 / / /蓝线和其实也很好维护,为了维护他们,我们需要再维护一个区间全部乘积,这里就不在赘述啦。

没骗你吧,就是套路题。

#include <cstdio>
#include <cstring>
#include <iostream>
using namespace std;
const int M = 100005;
const int MOD = 998244353;
int read()
{
	int x=0,f=1;char c;
	while((c=getchar())<'0' || c>'9') {if(c=='-') f=-1;}
	while(c>='0' && c<='9') {x=(x<<3)+(x<<1)+(c^48);c=getchar();}
	return x*f;
}
int n,m;
struct Matrix
{
	int a[4][4];
	Matrix() {memset(a,0,sizeof a);}
	Matrix operator * (const Matrix &b) const
	{
		Matrix r;
		for(int i=1;i<=3;i++)
			for(int j=1;j<=3;j++)
				for(int k=1;k<=3;k++)
					r.a[i][k]=(r.a[i][k]+1ll*a[i][j]*b.a[j][k])%MOD;
		return r;
	}
	Matrix operator + (const Matrix &b) const
	{
		Matrix r;
		for(int i=1;i<=3;i++)
			for(int j=1;j<=3;j++)
				r.a[i][j]=(a[i][j]+b.a[i][j])%MOD;
		return r;
	}
	void print()
	{
		for(int i=1;i<=3;i++,puts(""))
			for(int j=1;j<=3;j++)
				printf("%d ",a[i][j]);
	}
}E,I,A;
struct node
{
	Matrix pr,sf,ml;int ans;
	node() {ans=0;}
	node operator + (const node &b) const
	{
		node r;
		r.ans=(1ll*ans+b.ans+(sf*b.pr).a[2][1])%MOD;
		r.pr=(pr)+(b.pr*ml);
		r.sf=(b.sf)+(sf*b.ml);
		r.ml=ml*b.ml;
		return r;
	}
}tr[4*M],tmp;
Matrix qkpow(Matrix a,int b)
{
	Matrix r=I;
	while(b>0)
	{
		if(b&1) r=r*a;
		a=a*a;
		b>>=1;
	}
	return r;
}
void insert(int i,int l,int r,int id,int v)
{
	if(l==r)
	{
		Matrix t=qkpow(A,v);
		tr[i].ans=t.a[2][1];
		tr[i].pr=tr[i].sf=tr[i].ml=I+t;
		return ;
	}
	int mid=(l+r)>>1;
	if(mid>=id) insert(i<<1,l,mid,id,v);
	else insert(i<<1|1,mid+1,r,id,v);
	tr[i]=tr[i<<1]+tr[i<<1|1];
}
void ask(int i,int l,int r,int L,int R)
{
	if(L>r || l>R) return ;
	if(L<=l && r<=R)
	{
		tmp=tmp+tr[i];
		return ;
	}
	int mid=(l+r)>>1;
	ask(i<<1,l,mid,L,R);
	ask(i<<1|1,mid+1,r,L,R);
}
signed main()
{
	I.a[1][1]=I.a[2][2]=I.a[3][3]=1;A.a[1][3]=2;
	A.a[1][1]=A.a[1][2]=A.a[2][1]=A.a[3][1]=A.a[3][3]=1;
	//qkpow(A,2).print();
	n=read();m=read();
	for(int i=1;i<=n;i++)
		insert(1,1,n,i,read());
	while(m--)
	{
		int id=read(),l=read(),r=read();
		if(id==1)
		{
			insert(1,1,n,l,r);
		}
		else
		{
			tmp.pr=tmp.sf=E;tmp.ml=I;tmp.ans=0;
			ask(1,1,n,l,r);
			printf("%d\n",tmp.ans);
		}
	}
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值