事先声明,本博客代码主要模仿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代码

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

NOI 2011 兔农 题解的更多相关文章

  1. 博弈论(二分图匹配):NOI 2011 兔兔与蛋蛋游戏

    Description Input 输入的第一行包含两个正整数 n.m. 接下来 n行描述初始棋盘.其中第i 行包含 m个字符,每个字符都是大写英文字母"X".大写英文字母&quo ...

  2. [BZOJ2432][Noi2011]兔农 矩阵乘法+exgcd

    2432: [Noi2011]兔农 Time Limit: 10 Sec  Memory Limit: 256 MB Description 农夫栋栋近年收入不景气,正在他发愁如何能多赚点钱时,他听到 ...

  3. 【BZOJ2432】【NOI2011】兔农(数论,矩阵快速幂)

    [BZOJ2432][NOI2011]兔农(数论,矩阵快速幂) 题面 BZOJ 题解 这题\(75\)分就是送的,我什么都不想写. 先手玩一下,发现每次每次出现\(mod\ K=1\)的数之后 把它减 ...

  4. 2432: [Noi2011]兔农 - BZOJ

    Description 农夫栋栋近年收入不景气,正在他发愁如何能多赚点钱时,他听到隔壁的小朋友在讨论兔子繁殖的问题. 问题是这样的:第一个月初有一对刚出生的小兔子,经过两个月长大后,这对兔子从第三个月 ...

  5. 【bzoj2432】【NOI2011】兔农

    题目描述 农夫栋栋近年收入不景气,正在他发愁如何能多赚点钱时,他听到隔壁的小 朋友在讨论兔子繁殖的问题. 问题是这样的:第一个月初有一对刚出生的小兔子,经过两个月长大后,这 对兔子从第三个月开始,每个 ...

  6. [HNOI2011]卡农 题解

    题目描述 众所周知卡农是一种复调音乐的写作技法,小余在听卡农音乐时灵感大发,发明了一种新的音乐谱写规则.他将声音分成 n 个音阶,并将音乐分成若干个片段.音乐的每个片段都是由 1 到 n 个音阶构成的 ...

  7. NOI 2021 部分题目题解

    最近几天复盘了一下NOI 2021,愈发发觉自己的愚蠢,可惜D2T3仍是不会,于是只写前面的题解 Day1 T1 可以发现,每次相当于将 \(x\to y\) 染上一种全新颜色,然后一条边是重边当且仅 ...

  8. 【BZOJ 2434】【NOI 2011】阿狸的打字机 fail树

    完全不会啊,看题解还看了好久,我是蒟蒻$QAQ$ $zyf$的题解挺好的:http://blog.csdn.net/clove_unique/article/details/51059425 $fai ...

  9. BZOJ2432 [Noi2011]兔农

    本文版权归ljh2000和博客园共有,欢迎转载,但须保留此声明,并给出原文链接,谢谢合作. 本文作者:ljh2000作者博客:http://www.cnblogs.com/ljh2000-jump/转 ...

随机推荐

  1. 浅谈Android高通(Qualcomm)和联发科(MTK)平台

    一款CPU好不好是要从多个方面考虑的,并不是说简简单单看一个主频.几个核心数就完了,更重要的是它的综合实力到底有多强,这里面当然也会牵扯到价格问题,性能相似当然是便宜的获胜,这是毋庸置疑的. 事实上, ...

  2. 什么是AIFF?

    AIFF是音频交换文件格式(Audio Interchange File Format)的英文缩写,是Apple公司开发的一种声音文件格式,被Macintosh平台及其应用程序所支持,Netscape ...

  3. ORA-09925: Unable to create audit trail file

    当我修改ORACLE_SID为新的SID,想进行数据库还原时,用sqlplus报如下错误 [oracle@dbtest ~]$ sqlplus / as sysdba SQL Production : ...

  4. Quartz Cron 生成工具

    /** * 每周期 */ function everyTime(dom) { var item = $("input[name=v_" + dom.name + "]&q ...

  5. Linux日志系统

    常见的日志 常见的日志一般存储在/var/log中.常见的日志查看使用:ls/ll,cat/more/less查看即可:wtmp,lastlog使用last和lastlog提取其信息即可 配置日志 较 ...

  6. Delphi的Anymouse方法探秘

    匿名函数是用Interface来实现的,具体细节可以看http://www.raysoftware.cn/?p=38匿名函数还是非常方便的.比如自己封装的异步调用.Async(procedure(AP ...

  7. 年度调查 看看 2016 年 Go 语言调查结果

    Go 语言官方博客公布了 2016 年 Go 语言使用调查. 在 3,595 名被调查者中,89% 称他们在工作中或工作之外用 Go 编程:63% 称他们的工作是 Web 开发,但只有 9% 的人只从 ...

  8. C# winform 主界面打开并关闭登录界面

    在winform 界面编程中,我们有时候要在主界面打开之前先显示登录界面,当登录界面用户信息校验正确后才打开主界面,而这时登陆界面也完成使命该功成身退了. 目前有两种方法可实现: 方法1. 隐藏登录界 ...

  9. Windows下用VC与QT编译MPI程序入门

    MPI是信息传递接口的简称,常用来进行进程间.机器间的通信与并行计算.一般而言,MPI都会部署在*nix系统下,在Windows下面直接编译.配置MPI并不容易.本文利用MS提供的编译好的MPI的版本 ...

  10. c++实现游戏开发中常用的对象池(含源码)

    c++实现游戏开发中常用的对象池(含源码) little_stupid_child2017-01-06上传   对象池的五要素: 1.对象集合 2.未使用对象索引集合 3.已使用对象索引集合 4.当前 ...