[NOI2011]兔农(斐波那契数列+乘法逆+矩阵加速)

几乎是抄的,啥都不要说了

贴上策爷和VFK的链接好了

http://jcvb.is-programmer.com/posts/39528.html

http://vfleaking.blog.163.com/blog/static/174807634201341721051604/


还是写两句吧。关键是将新数列每个元素模k后写出,在经减1余0的元素后面换行,并找规律

发现每行前两个数相同,因此每行第i个数为 Fibonacc[i]*行首的数,行末数为0,行长len[i]为:第一个模k等于[行首的数在模k下的逆元]的Fibonacc数的位置,每一行的后继行行首数字next[i]行首的数*Fibonacc[len[i]-1]%k

把这些信息预处理出来会方便一些

还发现有几行会循环出现,考虑矩阵加速优化之:

从第1行开始,每行内部用矩阵加速处理 
若n很大,进入循环,先处理一个循环节,然后在循环节之间也用矩阵加速处理 
最后,不足一循环的,每行矩阵加速处理 

注意特殊情况:不出现循环,即某一行的行首在模k下不存在逆元,或其逆元不等于任何Fibonacc[x]%k,此时设这一行无限长,无后继行 


#include
#include
#include
typedef long long LL;
const LL INF=2000000000000000000ll;//结尾加"ll"表示long long型常量 
LL len[1000005]={0};
int fib[6000005]={0},first[1000005]={0},next[1000005]={0},vis[1000005]={0};
LL mod;
struct juzhen
{
	LL s[5][5];
	juzhen()
	{
		memset(s,0,sizeof(s));
	}
};
void exgcd(int a,int b,int& d,int& x,int& y)
{
	int t;
	if(b==0)
	{
		d=a;
		x=1;
		y=0;
		return;
	}
	exgcd(b,a%b,d,x,y);
	t=x;
	x=y;
	y=t-(a/b)*y;
}
int ni(int a,int n)
{
	int d=0,x=0,y=0;
	exgcd(a,n,d,x,y);
	if(d!=1) return 0;
	return (x+n)%n;
}
juzhen cheng(juzhen a,juzhen b)
{
	juzhen res;
	int i,j,k;
	for(i=1;i<=3;i++)
		for(j=1;j<=3;j++)
		{
			for(k=1;k<=3;k++)
				res.s[i][j]+=a.s[i][k]*b.s[k][j];
			res.s[i][j]%=mod;
		}
	return res;
}
juzhen ksm(juzhen a,LL n)
{
	juzhen res;
	res.s[1][1]=res.s[2][2]=res.s[3][3]=1;
	if(n==0) return res;
	if(n==1) return a;
	res=ksm(a,n/2);
	res=cheng(res,res);
	if(n%2==1) res=cheng(res,a);
	return res;
}
int main()
{
	juzhen A,B,C,Z;
	LL n,len_cir=0;
	int k,i,cir;
	scanf("%lld%d%lld",&n,&k,&mod);
	fib[1]=fib[2]=1;
	for(i=3;i==3||fib[i-1]!=1||fib[i-2]!=1;i++)//预处理出Fibonacc数列每个元素模k的值,直到出现循环 
	{
		fib[i]=(fib[i-1]+fib[i-2])%k;
		if(first[fib[i]]==0) first[fib[i]]=i;//记录余数最早出现的位置 
	}
	for(i=1;i0)
	{
		do
		{
			C=cheng(C,ksm(A,len[i]));
			C=cheng(C,B);
			len_cir+=len[i];//计算圈上总共有多少项 
			i=next[i];
		}
		while(i!=cir);
		Z=cheng(Z,ksm(C,n/len_cir));
		n%=len_cir;
		for(i=cir;;i=next[i])//剩下不完整圈的部分 
		{
			if(n


你可能感兴趣的:(矩阵,数论,思路题)