事先声明,本博客代码主要模仿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. delphi资源文件的使用

    delphi资源文件的使用 资源文件(*.res)通过编译指令 $R 关联, 譬如工程文件 Project1 中的 {$R *.res} 就是关联 Project1.res 资源文件, 我们直接写作 ...

  2. Winform入门见解

    winform算是C#比较快速的入门的一个了,简单的控件拖拽然后写上每个控件对应的事件.然后就可以了.需要美观的点 可以用Skin皮肤就完成了.我们先不说复杂的,就来个普通的三层架构来增删改查 分页和 ...

  3. WPF ListboxItem 双击事件 Command绑定

    <ListBox x:Name="Lb" HorizontalAlignment="Left" Height="600" Vertic ...

  4. lvcreate命令

    lvcreate Command Examples on Linux : 1. The following command creates a logical volume 15 gigabytes ...

  5. Delphi 编写IC控件

    编写控件的基本步骤 1.确定一个祖先类 2.创建一个组件单元 3.在新控件中添加属性.方法和事件 事件定义方法如下: type private FOnClick:TNotifyEvent ;//( 声 ...

  6. MinGW gcc 生成动态链接库 dll 的一些问题汇总(由浅入深,很详细)

    网络上关于用 MinGW gcc 生成动态链接库的文章很多.介绍的方法也都略有不同.这次我在一个项目上刚好需要用到,所以就花了点时间将网上介绍的各种方法都实验了一遍.另外,还根据自己的理解试验了些网上 ...

  7. 3022Java_运算

    运算 1.运算符分类 算术运算符 二元运算符 +,-,*,/,% 一元运算符 ++,-- 赋值运算符   = 扩展运算符 +=,-=,*=,/= 关系运算符 >,<,>=,<= ...

  8. Tido c++树状数组知识讲解(转载)

    树状数组可以用来动态计算前缀和,可以随时进行更新 而普通的前缀和只是静态的         

  9. ChannelPipeline----贯穿io事件处理的大动脉

    ChannelPipeline贯穿io事件处理的大动脉 上一篇,我们分析了NioEventLoop及其相关类的主干逻辑代码,我们知道netty采用线程封闭的方式来避免多线程之间的资源竞争,最大限度地减 ...

  10. 音乐盒子mplayer问题review

    背景:实现全志R16-linux开发板上的mplayer的调试 一.mplayer软件架构:   这里详细介绍了alsa的相关知识 二.问题解决1:播放卡顿 0.问题描述:播放过程中会突然发生卡顿,就 ...