[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, ...
随机推荐
- 前端面试之angular JS
1. angular的数据绑定采用什么机制?详述原理 angularjs的双向数据绑定,采用脏检查(dirty-checking)机制.ng只有在指定事件触发后,才进入 $digest cycle : ...
- C++ 实现一个信号量
C++ 实现一个信号量 信号量有很多应用场景,事实上只要是生产者-消费者模型,一般都需要一个信号量来控制. POSIX接口是有PV信号量API的.但C++标准没有.下面是一个PV信号量的简单实现.有些 ...
- 阿里云CentOS部署小笔记
快毕业了,我用近两周的时间完成了一个nodeJs+Vue-Cli+Mysql的毕业设计,到了部署的时候了. 然而,博主使用Linux的经验有限得很,所以只能自己慢慢地填坑了. 一.准备工作 1)阿里云 ...
- JavaScript AJAX实例
原生JS实现AJAX: // method : 请求方式 POST/GET; // url: 如果为GET方式的话url里面要带参数 // obj: 准备好的容器,方便储存拿到的数据 function ...
- ELK学习总结(1-3)倒排索引
1.倒排索引(反向索引) 一种索引方法,用来存储在全文检索下某个单词在一个/组文档中的存储位置. 常规索引,文档->关键词,费时,得把一个文档全部遍历一遍 倒排索引,关键词->文档,全文搜 ...
- 【原生js实现一键回到顶部】
<!DOCTYPE html><html> <head> <meta charset="UTF-8"> <title>& ...
- leetcode算法: Find Largest Value in Each Tree Row
'''You need to find the largest value in each row of a binary tree.Example:Input: 1 / \ 3 2 / \ \ 5 ...
- Python/MySQL(二、表操作以及连接)
Python/MySQL(二.表操作以及连接) mysql表操作: 主键:一个表只能有一个主键.主键可以由多列组成. 外键 :可以进行联合外键,操作. mysql> create table y ...
- 前端 jQuery
一.jQuery是什么? <1>jQuery由美国人John Resig创建,至今已吸引了来自世界各地众多JavaScript高手加入其team. <2>jQuery是继pro ...
- .net core 使用阿里云短信发送SMS
阿里云官方的skd(aliyun-net-sdk-core,aliyun-net-sdk-dysmsapi)在dnc中发送短信会出错,nuget上的包貌似也一样不管用.直接改下sdk当然也可以,但就发 ...