ZOJ 3772 —— Calculate the Function(矩阵乘法)

题目链接:http://acm.zju.edu.cn/onlinejudge/showProblem.do?problemId=5235

这道题的描述很友好,很好读,于是不解释题意了。。。

这道题的解法貌似也不少,我就说我比赛时用的做法吧。

首先是那个递推的式子,很容易让人联想到斐波那契数列的感脚,而斐波那契的求解可以用矩阵加速,那么这道题可以尝试构造个矩阵。

那么对于Fi+1 和 Fi,要计算出Fi+2的话,按照递推式有:


那么,如果我们要求对应的F(r)的话,可以通过先将中间的矩阵乘起来,直接拿区间的前两项乘上这个区间就好了。

那么问题就是,怎么获取这个矩阵?如果是求和,如果已经处理好前缀S[r],那么l到r这个区间上的和就可以用S[r]-S[l-1]得到,利用这种思想来考虑这个问题的话,

我们可以先计算出所有矩阵的前缀积S[i],这个没问题,构造一个就用前面的乘过来就行了,我们用X[i][j]来表示从i到j的矩阵的积。

对于我们的问题,如果L等于R,或是只比R小一,那么我们可以直接输出A[R]的值;

对于差值更大的,其实我们中间要乘的矩阵就是X[L+2][R],我们知道S[L+1] * X[L+2][R] = S[R],利用矩阵运算的性质,我们可以得到

X[L+2][R] = REV[L+1] * S[R],其中REV[L+1]是S[L+1]的逆,也就是说两者相乘得到一个单位矩阵。

 至于求逆,每个矩阵对应的逆可以在计算出的前缀积的时候顺便计算,计算逆最直接的做法就是列方程求解(我就是这样干的),不过貌似有更高级的做法,毕竟这里2*2的矩阵手算可以,要是矩阵大了估计就呵呵了。。。


剩下的工作就是细心点慢慢码了。。。

PS:第一次写矩阵求逆,写得好搓。。。

#include<cstdio>
#include<cstring>
#define LL long long
#define mod 1000000007
inline void in(int& x){
	x=0;
	char c=getchar();
	while(c<48 || c>57)	c=getchar();
	while(c>=48 && c<=57){
		x = x*10+c-48;
		c = getchar();
	}
}
struct Matrix{
	LL x[2][2];
};
int t, n, m, l, r;
int a[100001];
Matrix s[100001], re[100001], tmp;
LL ans;
Matrix mul(Matrix& A, Matrix& B){
	Matrix c;
	for(int i=0; i<2; i++){
		for(int j=0; j<2; j++){
			c.x[i][j]=0;
			for(int k=0; k<2; k++){
				c.x[i][j] += (A.x[i][k]*B.x[k][j])%mod;
				c.x[i][j] %= mod;
			}
		}
	}
	return c;
}
void gcd(LL A, LL B, LL& d, LL& x, LL& y){
	if(!B){
		d=A; x=1; y=0;
	}
	else{
		gcd(B, A%B, d, y, x);
		y -= x*(A/B);
	}
}
LL inv(LL A){
	LL d, x, y;
	gcd(A, mod, d, x, y);
	return (x+mod)%mod;
}
Matrix rev(Matrix& A){
	Matrix B;
	LL g1 = (A.x[0][0]*A.x[1][1]%mod - A.x[0][1]*A.x[1][0]%mod)%mod;
	g1 = (g1+mod)%mod;
	LL ni1 = inv(g1);
	LL ni2 = inv(mod-g1);

	LL aa, bb, cc, dd;
	cc = A.x[1][0]*ni2%mod;
	aa = -1 * (A.x[1][1]*cc%mod)*inv(A.x[1][0])%mod;
	aa = (aa%mod+mod)%mod;

	dd = A.x[0][0]*ni1%mod;
	bb = -1 * (A.x[0][1]*dd%mod)*inv(A.x[0][0])%mod;
	bb = (bb%mod+mod)%mod;

	B.x[0][0]=aa;
	B.x[0][1]=bb;
	B.x[1][0]=cc;
	B.x[1][1]=dd;
	return B;
}
int main(){
	in(t);
	while(t--){
		in(n); in(m);
		s[0].x[0][0]=s[0].x[1][1]=1;
		s[0].x[0][1]=s[0].x[1][0]=0;
		for(int i=1; i<=n; i++){
			in(a[i]);
			s[i].x[0][0]=s[i].x[0][1]=1;
			s[i].x[1][0]=a[i];
			s[i].x[1][1]=0;
			s[i] = mul(s[i-1], s[i]);
			re[i] = rev(s[i]);
		}
		while(m--){
			in(l); in(r);
			if(r<=l+1)	printf("%d\n", a[r]);
			else{
				tmp = mul(re[l+1], s[r]);
				LL A=a[l+1], B=a[l];
				ans = (A*tmp.x[0][0])%mod + (B*tmp.x[1][0])%mod;
				ans = (ans%mod+mod)%mod;
				printf("%lld\n", ans);
			}
		}
	}
	return 0;
}


  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值