zoukankan      html  css  js  c++  java
  • BZOJ 2432 兔农

    Description

    农夫栋栋近年收入不景气,正在他发愁如何能多赚点钱时,他听到隔壁的小朋友在讨论兔子繁殖的问题。
    问题是这样的:第一个月初有一对刚出生的小兔子,经过两个月长大后,这对兔子从第三个月开始,每个月初生一对小兔子。新出生的小兔子生长两个月后又能每个月生出一对小兔子。问第n个月有多少只兔子?
    聪明的你可能已经发现,第(n)个月的兔子数正好是第(n)(Fibonacci)(斐波那契)数。栋栋不懂什么是Fibonacci数,但他也发现了规律:第(i+2)个月的兔子数等于第(i)个月的兔子数加上第(i+1)个月的兔子数。前几个月的兔子数依次为:
    (1;1;2;3;5;8;13;21;34 cdots)
    栋栋发现越到后面兔子数增长的越快,期待养兔子一定能赚大钱,于是栋栋在第一个月初买了一对小兔子开始饲养。
    每天,栋栋都要给兔子们喂食,兔子们吃食时非常特别,总是每k对兔子围成一圈,最后剩下的不足(k)对的围成一圈,由于兔子特别害怕孤独,从第三个月开始,如果吃食时围成某一个圈的只有一对兔子,这对兔子就会很快死掉。
    我们假设死去的总是刚出生的兔子,那么每个月的兔子数仍然是可以计算的。例如,当(k=7)时,前几个月的兔子数依次为:
    (1;1;2;3;5;7;12;19;31;49;80 cdots)
    给定(n),你能帮助栋栋计算第(n)个月他有多少对兔子么?由于答案可能非常大,你只需要告诉栋栋第(n)个月的兔子对数除(p)的余数即可。

    Input

    输入一行,包含三个正整数(n, k, p)

    Output

    输出一行,包含一个整数,表示栋栋第(n)个月的兔子对数除p的余数。

    Sample Input

    6 7 100

    Sample Output

    7

    HINT

    (1 le N le 10^{18})
    (2 le K le 10^{6})
    (2 le P le 10^9)

    一道很好的矩阵乘法的题目,综合性感觉蛮强的。

    (k=7)为例,考虑(f[i]%k)组成的序列:
    1,1,2,3,5,0,
    5,5,3,0,
    3,3,6,2,0,
    2,2,4,6,3,2,5,0,5,5,3,0,
    3,3,6,2,0,
    (cdots)

    把减(1)(0)的位置标出,并以这些(0)为界分段,可以发现:
    ①每段开头必为相同两数,它恰是上一段的最末一位非(0)数;由于总共只有(k-1)种余数,所以不超过(k)段就会出现循环(如果有的话),比如上面(k=7)时的第(3,4)段就是循环节。
    ②记斐波那契数列为(fib[i])。假如某段段首数字为(x),那么这一段内第i个数即为(x imes fib[i]%k)。若记这一段长度为(len),则有(x imes fib[len] equiv 1(mod k))

    现在我们试图找到整个数列的循环结构:根据上式,①求x的逆元得到(fib[len]),②由(fib[len])得知(len),③用(x imes fib[len-1]%k)算出下一段的段首,重复操作直到发现循环(或者发现这一段永远不终止)。
    至于具体实现:①扩欧或者欧拉定理②预处理(indfib[y])数组,表示斐波那契数列中模(k)(y)的数第一次出现的下标(开头的两个1不算)③预处理(fib[i])(k)的值。有一个结论:斐波那契数列模(k)后一定是0,1,1开头的纯循环,而且这个循环节的长度$ le 6k((不知具体怎么证。。),所以只需暴力算)fib(数组并同时记录)indfib[](,发现循环即停止。 注意,假如第①步不存在逆元,或者第②步不存在符合的)len$,那么这一段将永远不会终止(比如k=8时就是这样),那么整个数列就不存在循环了(可以视作最后一段的长度为无穷大)。

    接下来考虑如何用矩阵乘法计算(f[n]%p)
    两个重要矩乘:

    分别记这两个(3 imes 3)矩阵为(A,B)。令初始矩阵为,通过对其不断右乘(A)(B)便能实现累加、减(1)两种操作。对于分出的每一段算出一个矩阵(A^{len} imes B),表示这一段的“效果”。

    接下来是喜闻乐见的分类讨论时间:假如整个数列是循环的,判断第n项是否在混循环的那部分里,若是则直接把前面几段乘起来,n所在这一段的零头直接用A的次幂算;若不是则先把混循环全部乘起来,然后把循环节全部乘起来,算出循环次数再快速幂,然后再像刚才一样算零头乘上去。若数列不循环倒方便些,也与上面类似,不多说了;如果长度无限,直接矩乘累加即可。

    上述内容引自http://jcvb.is-programmer.com/posts/39528.html

    最后贴一份自己的代码:

    #include<cstring>
    #include<cstdio>
    #include<cstdlib>
    using namespace std;
    
    typedef long long ll;
    #define maxn (1000010)
    ll n,K,p,fib[10000010],len[maxn],indfib[maxn],inv[maxn];
    bool vis[maxn];
    
    struct Matrix
    {
    	ll a,b; ll s[4][4];
    	Matrix () { memset(s,0,sizeof(s)); }
    	friend inline Matrix operator * (const Matrix &x,const Matrix &y)
    	{
    		Matrix z; z.a = x.a; z.b = y.b;
    		for (ll i = 1;i <= z.a;++i)
    			for (ll j = 1;j <= z.b;++j)
    				for (ll k = 1;k <= x.b;++k)
    					(z.s[i][j] += x.s[i][k]*y.s[k][j]%p)%=p;
    		return z;
    	}
    }save[maxn];
    
    inline ll exgcd(ll a,ll b,ll c)
    {
    	if (a == 0) return -1;
    	else if (c % a == 0) return c/a;
    	ll t = exgcd(b % a,a,((-c % a)+a)%a);
    	if (t == -1) return -1;
    	return (t*b+c)/a;
    }
    
    inline Matrix qsm(Matrix x,ll y)
    {
    	Matrix ret;
    	ret.a = ret.b = x.a; ret.s[1][1] = ret.s[2][2] = ret.s[3][3] = 1;
    	for (;y;y >>= 1,x = x*x)
    		if (y & 1) ret = ret*x;
    	return ret;
    }
    
    int main()
    {
    	freopen("2432.in","r",stdin);
    	freopen("2432.out","w",stdout);
    	scanf("%lld %lld %lld",&n,&K,&p);
    	fib[1] = fib[2] = 1;
    	for (ll i = 3;;++i)
    	{
    		fib[i] = fib[i-1] + fib[i-2];
    		if (fib[i] >= K) fib[i] -= K;
    		if (!indfib[fib[i]]) indfib[fib[i]] = i;
    		if (fib[i] == 1&&fib[i-1] == 1) break;
    	}
    	Matrix ans,mul,dec; bool sign = false;
    	ans.a = 1; ans.b = 3; ans.s[1][1] = ans.s[1][3] = 1;
    	mul.a = mul.b = 3; mul.s[1][2] = mul.s[2][1] = mul.s[2][2] = mul.s[3][3] = 1;
    	dec.a = dec.b = 3; dec.s[1][1] = dec.s[2][2] = dec.s[3][3] = 1; dec.s[3][2] = -1;
    	for (ll t = 1;n;)
    	{
    		if (!inv[t]) inv[t] = exgcd(t,K,1);
    		if (inv[t] == -1) { ans = ans*qsm(mul,n); n = 0; }
    		else
    		{
    			if (!vis[t]||sign)
    			{
    				vis[t] = true;
    				if (!indfib[inv[t]])
    				{
    					ans = ans*qsm(mul,n); n = 0;
    				}
    				else
    				{
    					len[t] = indfib[inv[t]];
    					if (n >= len[t])
    					{
    						n -= len[t];
    						save[t] = qsm(mul,len[t])*dec;
    						ans = ans*save[t];
    						(t *= fib[len[t]-1])%=K;
    					}
    					else { ans = ans*qsm(mul,n); n = 0; }
    				}
    			}
    			else
    			{
    				ll cnt = 0; Matrix ret; ret.a = ret.b = 3; ret.s[1][1] = ret.s[2][2] = ret.s[3][3] = 1;
    				for (ll i = t*fib[len[t]-1]%K;i != t;(i *= fib[len[i]-1])%=K)
    					cnt += len[i],ret = ret * save[i];
    				cnt += len[t],ret = save[t]*ret;
    				ans = ans*qsm(ret,n/cnt); n %= cnt;
    				sign = true;
    			}
    		}
    	}
    	printf("%lld",(ans.s[1][2]+p)%p);
    	fclose(stdin); fclose(stdout);
    	return 0;
    }
    
    
  • 相关阅读:
    学习、发现和创造一切皆有规律
    Ubuntu12.04下建立交叉编译环境、使用QEMU模拟CortexA9、QEMU运行uboot
    基于ARM的SoC设计入门[zz]
    ARM指令集详解[zz]
    电子工程自学步骤与书籍非电子专业
    IC设计的前端和后端[zz]
    [转]用C#获取IE临时文件
    二行代码解决全部网页木马(含iframe/script木马)(zt)
    winform 分页控件,源码下载
    在UpdatePanel中GridView导出EXECL问题
  • 原文地址:https://www.cnblogs.com/mmlz/p/4298169.html
Copyright © 2011-2022 走看看