事先声明,本博客代码主要模仿accepoc,且仅针对一般如本博主一样的蒟蒻。

  这道题不得不说数据良心,给了75分的水分,但剩下25分真心很难得到,因此我们就来讲一讲这剩下的25分。

  首先,有数据可知他无心炸long long,因此高精度什么的倒是不用,但10^18的数据范围明显O(n)递推使不靠谱的,又因为本题是建立在斐波那契数列之上,考虑矩阵快速幂优化。

  首先先科普一下,斐波那契数列在mod一个数后会形成一个大循环,最大不超过6K(我不会证,有神犇路过望不吝赐教)。因此我们需要一个vi[i]数组记录斐波那契数列在%k下第一次出现i对应的是第几个斐波那契数(一会有用)。至于什么时候跳出循环嘛,第i个数和i-1个数为1时就跳出。

  

  scanf("%lld%lld%lld",&n,&k,&p);
fib[]=fib[]=;
for(int i=;;i++)
{
fib[i]=fib[i-]+fib[i-];
fib[i]%=k;
if(!vi[fib[i]]) vi[fib[i]]=i;
if(fib[i]==&&fib[i-]==)break;
}

预处理部分

  然后说本题最主要的部分,也就是最后25分,让我们以k=7时举例,那么新数列就为

  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, 
  ⋯

  为0的地方就是原本%k为1的地方,不难看出这里有一个新的循环节,大家可以再举举别的例子,可以发现大部分数都是这样对,大部分。比如8貌似就不行。

  其次,我们还可以注意到每一行除以第一个数都是一个斐波那契数列(当然是没有模过之前),因此,设该行长度为len,行首元素为x,那么x*f[len]%k==1,大家想到了什么,逆元!

  逆元是个好东西,它可以帮助我们判断是否有循环节,如果x无逆元,说明无循环节,矩阵乘搞到头,那么,如果x有逆元呢?还记得之前的vi数组吗,对,由vi即可判断它到底是该行第几个,如果vi不存在,还是按上面处理。如果存在,直接推出来len,然后进行矩阵快速幂,因为mod1要自减,因此矩阵有两个,为了方便,我们称第一个为nol,第二个为de

  

  

  上面比较常见,下面一个就是在行末才用的到的矩阵,来消掉1。

  然后就是fin[i]数组了,用来标记以i为开头的数列是否已经被结算过,和res[i]搭配着用,res[i]就是以i为开头的数列其中nol和de相乘所得的最终结果,这样在利用循环节的时候就可以很方便的使用了。我们利用行首元素进行循环因为每个行首元素对应且仅对应唯一的数列,因此先通过之前处理出循环节所对应的矩阵求出一个包含整个循环节的矩阵,直接快速幂乱搞,把n%循环节总行数,将flag打上标记,用之前处理出的单行再次进行矩阵乘,直到乘完为止。

  for(long long t=;n;) //枚举各行开头
{
if(!inv[t]) inv[t]=exgcd(t,k,);
if(inv[t]==-)
{
ans=ans*ksm(nol,n);
break;
}
else
{
if(!fin[t]||flag)
{
fin[t]=;
if(!vi[inv[t]])
{
ans=ans*ksm(nol,n);
break;
}
len[t]=vi[inv[t]];
if(n>=len[t])
{
n-=len[t];
res[t]=ksm(nol,len[t])*de;
ans=ans*res[t];
(t*=fib[len[t]-])%=k;
}
else
{
ans=ans*ksm(nol,n);//对剩下残余的n直接处理
break;
}
}
else
{
long long js=;
node ret(,);
ret.a[][]=ret.a[][]=ret.a[][]=;
for(long long i=t*fib[len[t]-]%k;i!=t;(i*=fib[len[i]-])%=k)
{
js+=len[i];
ret=ret*res[i];
}
js+=len[t];
ret=res[t]*ret;
ans=ans*ksm(ret,n/js);
n%=js;
flag=;
}
}
}

矩阵乘

  最后在说几个小坑,第一,尽管k在int范围内,但并不意味着我们就可以放心大胆的使用Int了毕竟在中间计算时会溢出,第二,矩阵乘并不满足交换律,虽然不小心把某些矩阵乘位置搞反并不会全WA,但当怎么看都觉得代码对的时候检查一下这个也是很有必要的。

  对于这道题毕竟是NOI难度(虽然在cogs上只有两星半),如果实在无法理解上面的讲解可以自己先抄一下代码,根据代码去理解。

  

 #include<iostream>
#include<cstdlib>
#include<cstdio>
#include<cstring>
#include<queue>
#include<algorithm>
#include<cmath>
using namespace std;
long long k,p;
long long fib[];
int vi[];
long long exgcd(long long a,long long b,long long c){ //求逆元
if(a==)return -;
else if(c%a==) return c/a;
long long t=exgcd(b%a,a,((-c%a)+a)%a);
if(t==-)return -;
return (t*b+c)/a;
}
struct node{
int n,m;
long long a[][];
node(){}
node(int x,int y){
n=x,m=y;
memset(a,,sizeof(a));
}
node operator*(const node &b)
{
node ans(n,b.m);
for(int i=;i<n;i++)
{
for(int j=;j<b.m;j++)
{
for(int k=;k<m;k++)
{
(ans.a[i][j]+=(a[i][k]*b.a[k][j])%p)%=p;
}
}
}
return ans;
}
}res[];
node ksm(node a,long long n){
long long x=n;
node ans(a.n,a.m);
ans.a[][]=ans.a[][]=ans.a[][]=;
while(x)
{
if((x&)) ans=ans*a;
a=a*a;
x>>=;
}
return ans;
}
long long n,inv[],len[];
bool fin[];
node ans,nol,de;
void solve(){
nol.n=nol.m=de.n=de.m=;
bool flag=;
ans.n=,ans.m=;
ans.a[][]=ans.a[][]=;
nol.a[][]=nol.a[][]=nol.a[][]=nol.a[][]=;//对nol和ni矩阵初始化。
de.a[][]=de.a[][]=de.a[][]=;
de.a[][]=-;
for(long long t=;n;) //枚举各行开头
{
if(!inv[t]) inv[t]=exgcd(t,k,);
if(inv[t]==-)
{
ans=ans*ksm(nol,n);
break;
}
else
{
if(!fin[t]||flag)
{
fin[t]=;
if(!vi[inv[t]])
{
ans=ans*ksm(nol,n);
break;
}
len[t]=vi[inv[t]];
if(n>=len[t])
{
n-=len[t];
res[t]=ksm(nol,len[t])*de;
ans=ans*res[t];
(t*=fib[len[t]-])%=k;
}
else
{
ans=ans*ksm(nol,n);//对剩下残余的n直接处理
break;
}
}
else
{
long long js=;
node ret(,);
ret.a[][]=ret.a[][]=ret.a[][]=;
for(long long i=t*fib[len[t]-]%k;i!=t;(i*=fib[len[i]-])%=k)//直接跳转下一行开头
{
js+=len[i];
ret=ret*res[i];
}
js+=len[t];
ret=res[t]*ret;
ans=ans*ksm(ret,n/js);
n%=js;
flag=;
}
}
}
}
int main(){
scanf("%lld%lld%lld",&n,&k,&p);
fib[]=fib[]=;
for(int i=;;i++)
{
fib[i]=fib[i-]+fib[i-];
fib[i]%=k;
if(!vi[fib[i]]) vi[fib[i]]=i;
if(fib[i]==&&fib[i-]==)break;
}
solve();
printf("%lld\n",(ans.a[][]+p)%p);
//while(1);
return ;
}

AC代码

  关于这道题打法有很多,个人代码是看着上方博客打出来的,希望读者不要拘泥于这一种打法。

最新文章

  1. c#缓存介绍(转)
  2. PHP Web实时消息后台服务器推送技术---GoEasy
  3. thinkphp学习笔记(一)
  4. java mybatis XML文件中大于号小于号转义
  5. T4 Templates
  6. 推荐一款JSON字符串查看器
  7. Ray Through Glasses
  8. Linux服务器下Java环境搭建
  9. js闭包深度讲解
  10. electron打包vue项目
  11. from中buttone 和 input type=&quot;button&quot; 区别
  12. 【Spring】16、注解事务 @Transactional
  13. (第十三周)评论Final发布II
  14. git bash中的快捷键
  15. C++中类的静态成员与实例成员的区别
  16. artTemplate js模板引擎动态给html赋值
  17. docker之故障问题解决方案
  18. Spring学习总结六——SpringMVC一
  19. 170717、springboot编程之mybatis数据库开发和aop拦截
  20. OOW 2015 MYSQL

热门文章

  1. Linux命令扫盲 之 sar
  2. C#高性能大容量SOCKET并发(九):断点续传
  3. Android零基础入门第64节:揭开RecyclerView庐山真面目
  4. 用Delphi实现文件下载的几种方法(三种使用控件的方法)
  5. C语言的setlocale和localtime函数(C++也可用)
  6. VCL比MFC好在哪里
  7. STL函数static void (* set_malloc_handler(void (*f)()))()与函数指针解析
  8. 系列教程 - java web开发
  9. CentOS7.5上FTP服务的安装与使用
  10. nginx之gzip压缩