NOI 2011 兔农 题解
事先声明,本博客代码主要模仿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 兔农 题解的更多相关文章
- 博弈论(二分图匹配):NOI 2011 兔兔与蛋蛋游戏
Description Input 输入的第一行包含两个正整数 n.m. 接下来 n行描述初始棋盘.其中第i 行包含 m个字符,每个字符都是大写英文字母"X".大写英文字母&quo ...
- [BZOJ2432][Noi2011]兔农 矩阵乘法+exgcd
2432: [Noi2011]兔农 Time Limit: 10 Sec Memory Limit: 256 MB Description 农夫栋栋近年收入不景气,正在他发愁如何能多赚点钱时,他听到 ...
- 【BZOJ2432】【NOI2011】兔农(数论,矩阵快速幂)
[BZOJ2432][NOI2011]兔农(数论,矩阵快速幂) 题面 BZOJ 题解 这题\(75\)分就是送的,我什么都不想写. 先手玩一下,发现每次每次出现\(mod\ K=1\)的数之后 把它减 ...
- 2432: [Noi2011]兔农 - BZOJ
Description 农夫栋栋近年收入不景气,正在他发愁如何能多赚点钱时,他听到隔壁的小朋友在讨论兔子繁殖的问题. 问题是这样的:第一个月初有一对刚出生的小兔子,经过两个月长大后,这对兔子从第三个月 ...
- 【bzoj2432】【NOI2011】兔农
题目描述 农夫栋栋近年收入不景气,正在他发愁如何能多赚点钱时,他听到隔壁的小 朋友在讨论兔子繁殖的问题. 问题是这样的:第一个月初有一对刚出生的小兔子,经过两个月长大后,这 对兔子从第三个月开始,每个 ...
- [HNOI2011]卡农 题解
题目描述 众所周知卡农是一种复调音乐的写作技法,小余在听卡农音乐时灵感大发,发明了一种新的音乐谱写规则.他将声音分成 n 个音阶,并将音乐分成若干个片段.音乐的每个片段都是由 1 到 n 个音阶构成的 ...
- NOI 2021 部分题目题解
最近几天复盘了一下NOI 2021,愈发发觉自己的愚蠢,可惜D2T3仍是不会,于是只写前面的题解 Day1 T1 可以发现,每次相当于将 \(x\to y\) 染上一种全新颜色,然后一条边是重边当且仅 ...
- 【BZOJ 2434】【NOI 2011】阿狸的打字机 fail树
完全不会啊,看题解还看了好久,我是蒟蒻$QAQ$ $zyf$的题解挺好的:http://blog.csdn.net/clove_unique/article/details/51059425 $fai ...
- BZOJ2432 [Noi2011]兔农
本文版权归ljh2000和博客园共有,欢迎转载,但须保留此声明,并给出原文链接,谢谢合作. 本文作者:ljh2000作者博客:http://www.cnblogs.com/ljh2000-jump/转 ...
随机推荐
- Apache Cordova for ios环境配置
原文:Apache Cordova for ios环境配置 1.安装针对iOS的工具 https://technet.microsoft.com/ZH-cn/library/dn757054.aspx ...
- Windows软件在Linux上的等价/替代/模仿软件列表 (抄一个)
Last update: 16.07.2003, 31.01.2005, 27.05.2005 您可在以下网站发现本列表最新版:http://www.linuxrsp.ru/win-lin-soft/ ...
- 谷歌将为 Mac 和 Windows 用户推出新的备份和同步应用
据报道,谷歌将于 6 月 28 日面向 Mac 和 Windows 用户发布一款新的备份和同步应用(Backup and Sync app). Google 刚刚宣布将推出其备份和同步应用程序,该工具 ...
- shell转义符
转义是一种引用单个字符的方法. 一个前面放上转义符 (\)的字符就是告诉shell这个字符按照字面的意思进行解释, 换句话说, 就是这个字符失去了它的特殊含义. 在某些特定的命令和工具中, 比如ech ...
- UWP-消息提示(仿Android)
原文:UWP-消息提示(仿Android) 在UWP中的消息提示框(一)中介绍了一些常见的需要用户主动去干涉的一些消息提示框,接下来打算聊聊不需要用户主动去干涉的一些消息提示框.效果就是像双击退出的那 ...
- Hadoop Streaming框架学习(二)
1.常用Streaming命令介绍 使用下面的命令运行Streaming MapReduce程序: 1: $HADOOP_HOME/bin/hadoop/hadoop streaming args 其 ...
- 一顶博士帽能带来什么——访GOOGLE公司中国区总裁李开复
在读了博士生远潇给本报的来信后,GOOGLE公司中国区总裁李开复说,有这些困惑和担心,实际上是很多博士生们在读博士之前并没有认真地想过,自己是不是能耐得住寂寞做学问,是不是能抵御来自物质世界的诱惑 ...
- [铁人赛] ASP.NET Core 2 系列- 从头开始
来势汹汹的.NET Core似乎要取代.NET Framework,ASP.NET也随之发布.NET Core版本.虽说名称沿用ASP.NET,但相较于ASP.NET确有许多架构上的差异,可说是除了名 ...
- vuejs 项目引入微信jssdk
一.导入依赖包 npm i -S weixin-js-sdk 二.前端页面使用 import wx from 'weixin-js-sdk' export default { created() { ...
- lock和synchronized如何选择?
1.lock是一个接口,而synchronized是java关键字,synchronized是内置的语言实现. 2.synchronized在发生异常时,会自动释放线程占有的锁,因此不会导致死锁,而l ...