[BSGS算法]纯水斐波那契数列
学弟在OJ上加了道“非水斐波那契数列”,求斐波那契第n项对1,000,000,007取模的值,n<=10^15,随便水过后我决定加一道升级版,说是升级版,其实也没什么变化,只不过改成n<=10^30000000,并对给定p取模,0<p<2^31。一样很水嘛大家说对不对。
下面来简单介绍一下BSGS算法,BSGS(Baby steps and giant steps),又称包身工树大步小步法,听上去非常高端,其实就是一个暴力搜索。比如我们有一个方程,a^x≡b (mod c),x是未知数,怎么算?难道是什么神奇的数学公式?直觉和经验告诉我们这玩意儿并不好算,无奈的我们只好枚举x,欧拉dalao告诉我们a^φ(c)≡1 (mod c),再怎么不济,我们O(c)也能算出答案……平时我们常见的模数一般都有十亿左右,明显要T啦,怎么办?
别急,我们来回顾一个经典问题,给你有n个数的集合,问他有多少个子集和为x。(n<=100,000,x<=10^18)
别想了,我也不会做,只好把数据改一改,n<=20?暴力就过啦。n<=50?此时有个常见的做法,我们枚举前n/2个数的和,开个map存下,再枚举后n/2个数,用x减掉枚举出的和再去map里找……复杂度O(2^(n/2))(可能还要多个log)。我们再考虑下子集积?发现并没有区别,枚举前n/2个数的积,存下,再枚举后n/2个数,用x乘上枚举出的积的逆元(假设取模)……是不是想到了什么?我们像快速幂那样把a^x表示成a^1,a^2,a^4,a^8……的积,解那个方程不就是做子集积吗?实际上不用这么麻烦,我们把x表示成只有两位的k进制数,我们就只要枚举两位了,令x=Ak+B,则a^(Ak+B)≡b (mod c),移项得a^Ak≡b*a^-B (mod c),我们先枚举A,算出等式左边,存入map里,再枚举B,算出等式右边,去map里找,就能算出答案啦。要保证这个k进制数只有两位,k自然大约是c^0.5。实际上我们可以稍微把k改大一点(或者A多枚举一点),把x改成x=Ak-B,这样有什么好处呢?你代回去再移一次项就会发现,我们不用算逆元啦!于是我们得到一个较为高效的求解指数方程的算法,复杂度大概是O(c^0.5)(视具体实现可能会多个log)。
好了,我们回到斐波那契数列,10^30000000明显就算我们用某klogklogn的算法也不是很好受(纯属口胡)。实际上很容易想到,斐波那契对一个数取模必然有循环节,因为每一项只和前两项有关,两项数的取值最多p^2种(p为模数),所以循环节至多p^2。事实上,某篇论文证明了,斐波那契数列对p取模的循环节不会超过6p(其实论文中还给出了对于不同的p,循环节的计算方法,不过复杂度应该和接下来要讲的BSGS做法相同(理论上论文做法更优,但我们肯定不想总依靠玄学的论文吧),有兴趣的可以百度一下)。那么我们如何用BSGS算出循环节呢?假设斐波那契数列的递推矩阵为A,我们只要求出A^x≡A (mod p)的一个大于1的解就可以了,求解过程和整数方程并没有太大区别。求出循环节后把那巨长无比的数字串取模下,随便水过该题。
以下是加了一些奇怪的常数优化的代码……如果看的时候遇到一些不知道在干嘛的地方,跳过就好了。
#include<cstdio>
#include<cstring>
#include<map>
using namespace std;
#define ll long long
#define MN 30000050
#define K 115000
char s[MN+];
int mod;
struct mat
{
int z[][];
mat(){memset(z,,sizeof(z));}
mat operator*(mat b)
{
mat c;int i,j,k;
for(i=;i<;++i)for(j=;j<;++j)
for(k=;k<;++k)c.z[i][j]=(c.z[i][j]+(ll)z[i][k]*b.z[k][j])%mod;
return c;
}
friend bool operator<(mat a,mat b)
{
for(int i=;i<;++i)for(int j=;j<;++j)
{
if(a.z[i][j]<b.z[i][j])return true;
if(a.z[i][j]>b.z[i][j])return false;
}
}
}f,k,p;
mat pow(ll x)
{
mat r=f,t=f;
for(--x;x;x>>=,t=t*t)if(x&)r=r*t;
return r;
}
map<mat,int> mp;
int main()
{
int i,l,r,mid;ll x,x1,x2,x3,x4,rp;
f.z[][]=f.z[][]=f.z[][]=;
fread(s,,MN,stdin);
for(l=,r=MN;l<=r;)
if(s[mid=l+r>>])l=mid+;
else i=mid,r=mid-;
while(s[--i]<''||s[i]>'')s[i]=;
while(s[i]>=''&&s[i]<='')--i;
for(l=i;s[l]<''||s[l]>'';)s[l--]=;
while(s[++i]>=''&&s[i]<='')mod=(mod<<)+(mod<<)+s[i]-'',s[i]=;
for(p=k=pow(K),i=;i<=K;++i)p=p*k,mp[p]=i;
for(p=f,i=;i<=K;++i,p=p*f)if(x=mp[p]){rp=x*K-i;break;}
for(x1=x2=x3=x4=,i=;s[i];i+=)
x1=(x1*+s[i-]-'')%rp,
x2=(x2*+s[i-]-'')%rp,
x3=(x3*+s[i-]-'')%rp,
x4=(x4*+s[i ]-'')%rp;
x=(x1*+x2*+x3*+x4)%rp;
for(i-=;s[i];++i)x=((x<<)+(x<<)+s[i]-'')%rp;
p.z[][]=p.z[][]=;if(x<)x+=rp;
printf("%d",(p*pow(x-)).z[][]);
}
正常版
#include<cstdio>
#include<cstring>
#include<map>
using namespace std;
#define ll long long
#define MN 30000000
#define K 115000
char s[MN+];
int mod;
struct mat
{
int z[][];
mat(){memset(z,,sizeof(z));}
mat operator*(mat b)
{
mat c;int i,j,k;
for(i=;i<;++i)for(j=;j<;++j)
for(k=;k<;++k)c.z[i][j]=(c.z[i][j]+(ll)z[i][k]*b.z[k][j])%mod;
return c;
}
friend bool operator<(mat a,mat b)
{
for(int i=;i<;++i)for(int j=;j<;++j)
{
if(a.z[i][j]<b.z[i][j])return true;
if(a.z[i][j]>b.z[i][j])return false;
}
}
}f,k,p;
mat pow(ll x)
{
mat r=f,t=f;
for(--x;x;x>>=,t=t*t)if(x&)r=r*t;
return r;
}
map<mat,int> mp;
int main()
{
int i;ll x,rp;
f.z[][]=f.z[][]=f.z[][]=;
scanf("%s%d",s,&mod);
for(p=k=pow(K),i=;i<=K;++i)p=p*k,mp[p]=i;
for(p=f,i=;i<=K;++i,p=p*f)if(x=mp[p]){rp=x*K-i;break;}
for(i=x=;s[i];++i)x=((x<<)+(x<<)+s[i]-'')%rp;
p.z[][]=p.z[][]=;if(x<)x+=rp;
printf("%d",(p*pow(x-)).z[][]);
}
[BSGS算法]纯水斐波那契数列的更多相关文章
- Reverse反转算法+斐波那契数列递归+Reverse反转单链表算法--C++实现
Reverse反转算法 #include <iostream> using namespace std; //交换的函数 void replaced(int &a,int & ...
- 剑指offer-第二章算法之斐波拉契数列(青蛙跳台阶)
递归与循环 递归:在一个函数的内部调用这个函数. 本质:把一个问题分解为两个,或者多个小问题(多个小问题相互重叠的部分,会存在重复的计算) 优点:简洁,易于实现. 缺点:时间和空间消耗严重,如果递归调 ...
- javascript:算法之斐波那契数列
一 //1,1,2,3,5,8,13,21这个数列 斐波那契 数列(肥波哪弃) //得到第9项是几? /*******************************111111111递归的思想*** ...
- PHP算法之斐波那契数列(递归)
/*斐波那契数列 源代码分析 f(x) = 1 ; 当 x < 2 ; f(x) = f(x-1)+f(x-2); 当 x >= 2 ; 通项式为:fn ={((1+根号5)/2)^n-( ...
- Python算法_斐波那契数列(10)
写一个函数,输入 n ,求斐波那契(Fibonacci)数列的第 n 项.斐波那契数列的定义如下: F(0) = 0, F(1) = 1F(N) = F(N - 1) + F(N - 2), 其中 ...
- 斐波那契数列公式算法-JS实现
之前算斐波那契数列都是算前两个数相加实现的 比如0, 1, 1, 2, 3, 5, 8, 13, 21, 34, 55, 89, 144, 233,377,610,987,1597,2584,4181 ...
- 算法 递归 迭代 动态规划 斐波那契数列 MD
Markdown版本笔记 我的GitHub首页 我的博客 我的微信 我的邮箱 MyAndroidBlogs baiqiantao baiqiantao bqt20094 baiqiantao@sina ...
- 《BI那点儿事》Microsoft 时序算法——验证神奇的斐波那契数列
斐波那契数列指的是这样一个数列 0, 1, 1, 2, 3, 5, 8, 13, 21, 34, 55, 89, 144, 233,377,610,987,1597,2584,4181,6765,10 ...
- Java算法求最大最小值,冒泡排序,斐波纳契数列一些经典算法<不断更新中>
清明在家,无聊,把一些经典的算法总结了一下. 一.求最大,最小值 Scanner input=new Scanner(System.in); int[] a={21,31,4,2,766,345,2, ...
随机推荐
- JAVA反射机制基础概念
反射机制:所谓的反射机制就是java语言在运行时拥有一项自观的能力.通过这种能力可以彻底的了解自身的情况为下一步的动作做准备.下面具体介绍一下java的反射机制.这里你将颠覆原来对java的理解. J ...
- 十、Python练习----基础搭建飞机大战
只是简单的学习了pygame,实现飞机的摧毁还需要多张图片的切换,和sprite(碰撞精灵),还有多种音效的添加(如背景音乐.摧毁特效).以后再深入学习我只是练习一下python. 一.搭建界面(基于 ...
- Thinkphp框架部署步骤
Thinkphp框架部署步骤 thinkphp框架部署起来简单,但是由于步骤较多也容易遗忘: 这是安装了集成环境后的一个www根目录结构: 然后需要在这个目录下面创建一个文件夹做项目:thinkphp ...
- clang++ 链接问题 和 VS Code
clang++ 链接问题 和 VS Code 如果你在windows上使用clang 并且同时安装有vs和mingw, clang链接是会自动使用msvs, 链接时会有LINK error LINK ...
- LeetCode & Q53-Maximum Subarray-Easy & 动态规划思路分析
Array DP Divide and Conquer Description: Find the contiguous subarray within an array (containing at ...
- Spring中报"Could not resolve placeholder"的解决方案
除去properites文件路径错误.拼写错误外,出现"Could not resolve placeholder"很有可能是使用了多个PropertyPlaceholderCon ...
- kubernetes入门(06)kubernetes的核心概念(3)
一.API 对象 API对象是K8s集群中的管理操作单元.K8s集群系统每支持一项新功能,引入一项新技术,一定会新引入对应的API对象,支持对该功能的管理操作.例如副本集Replica Set对应的A ...
- Spring Security 入门(1-6-1)Spring Security - 配置文件解析和访问请求处理
1.在pom.xml中添加maven坐标 <dependency> <groupId>org.springframework.security</groupId> ...
- 详解Ajax请求(一)前言——同步请求的原理
我们知道,ajax是一种异步请求的方式,想要了解异步请求,就必须要先从同步请求说起.常见的同步请求的方式是form表单的提交,我们先从一种同步请求的示例说起. 我们希望输入姓名可以从后台得到身份证号. ...
- 判断一个字符串是不是一个合法的IP地址
最近在笔试的时候遇到碰一道算法题, 要求判断一个字符串是不是合法的ip地址. 将我的思路发出来分享一下,不一定正确,也不一定是最优的方法.希望能分享一些交流 要求用java或者c来实现,我的java代 ...