【洛谷P3938】斐波那契——杨子曰题目

【洛谷P3938】斐波那契——杨子曰题目

超链接:数学合集


题目背景
大样例下发链接:http://pan.baidu.com/s/1c0LbQ2 密码:jigg

题目描述
小 C 养了一些很可爱的兔子。 有一天,小 C 突然发现兔子们都是严格按照伟大的数学家斐波那契提出的模型来进行 繁衍:一对兔子从出生后第二个月起,每个月刚开始的时候都会产下一对小兔子。我们假定, 在整个过程中兔子不会出现任何意外。

小 C 把兔子按出生顺序,把兔子们从 1 开始标号,并且小 C 的兔子都是 1 号兔子和 1 号兔子的后代。如果某两对兔子是同时出生的,那么小 C 会将父母标号更小的一对优先标 号。

如果我们把这种关系用图画下来,前六个月大概就是这样的:

在这里插入图片描述

其中,一个箭头 A → B 表示 A 是 B 的祖先,相同的颜色表示同一个月出生的兔子。

为了更细致地了解兔子们是如何繁衍的,小 C 找来了一些兔子,并且向你提出了 m 个 问题:她想知道关于每两对兔子 a i a_i ai b i b_i bi,他们的最近公共祖先是谁。你能帮帮小 C 吗?

一对兔子的祖先是这对兔子以及他们父母(如果有的话)的祖先,而最近公共祖先是指 两对兔子所共有的祖先中,离他们的距离之和最近的一对兔子。比如,5 和 7 的最近公共祖 先是 2,1 和 2 的最近公共祖先是 1,6 和 6 的最近公共祖先是 6。

输入格式:
从标准输入读入数据。 输入第一行,包含一个正整数 m。 输入接下来 m 行,每行包含 2 个正整数,表示 a i a_i ai b i b_i bi

输出格式:
输出到标准输出中。 输入一共 m 行,每行一个正整数,依次表示你对问题的答案。

输入样例:

5 
1 1 
2 3 
5 7 
7 13 
4 12

输出样例:

1 
1 
2 
2 
4 

说明
【数据范围与约定】 子任务会给出部分测试数据的特点。如果你在解决题目中遇到了困难,可以尝试只解 决一部分测试数据。 每个测试点的数据规模及特点如下表:

在这里插入图片描述

特殊性质 1:保证 a i , b i a_i,b_i ai,bi均为某一个月出生的兔子中标号最大的一对兔子。例如,对于前六个月,标号最大的兔子分别是 1, 2, 3, 5, 8, 13。

特殊性质 2:保证 ∣ a i − b i ∣ ≤ 1 \left|a_i-b_i\right|\leq1 aibi1


所以这到底是一道图论题呢,还是一道数论题呢?

我陷入沉思……

顺便打个广告:

谈谈最近公共祖先(LCA倍增)——杨子曰算法
数学合集


首先,我们先要深度理解一下这个兔子和斐波那契数列的关系,我们列一张表看看:

月份这个月的总兔子数这个月出生的兔子数
11\
210
321
431
552
683
7135
8218

你会发现左边那一列和右边那一列都是斐波那契数列,两列错位了两个格子

我们再来观察一下这个编号,你会发现一些重要的东西

假设当前到了第n轮,总兔子数是 f [ n ] f[n] f[n],可以生小兔子一定是前 f [ n − 1 ] f[n-1] f[n1]只,不能生小兔子是后 f [ n − 2 ] f[n-2] f[n2]

到了下个月,兔子会多 f [ n − 1 ] f[n-1] f[n1]只,由于之前就有 f [ n ] f[n] f[n]只兔子,所以它们的编号会从 f [ n ] + 1 f[n]+1 f[n]+1开始编号

也就是说

第1只兔子的孩子是f[n]+1
第2只兔子的孩子是f[n]+2
第3只兔子的孩子是f[n]+3
……
第f[n-1]只兔子的孩子是f[n]+f[n-1]=f[n+1]

有没有发现妈妈和孩子之间的编号差是 f [ n ] f[n] f[n],那么现在给我们一个编号x我们怎么找到它的妈妈,或者说这个 f [ n ] f[n] f[n]我们要怎样才能得到捏?

机智的你一定会发现f[n]就是小于x的最大的斐波那契数

儿子编号=f[n]+妈妈编号

而妈妈编号又一定小于等于 f [ n − 1 ] f[n-1] f[n1],So,儿子编号一定小于等于 f [ n + 1 ] f[n+1] f[n+1]

So,我们只要找到小于x斐波那契数中最大的那一个就行了,然后减一下,我们就可以检索到它的妈妈了,那么我们就知道所有的母子关系了,然后我们就可以暴力地去找LCA了

不停地把当前编号小的往上跳,知道跳到同一个节点了就是LCA

OK,完事


c++代码:

#include<bits/stdc++.h>
#define ll long long
using namespace std;

ll f[65];

void init(){
	f[1]=1;
	f[2]=1;
	for (int i=3;i<=60;i++){
		f[i]=f[i-1]+f[i-2];
	}
}


ll query(ll x,ll y){
	if (x==y) return x;
	if (x<y) swap(x,y);
	int tmp=lower_bound(f+1,f+61,x)-f;
	return query(y,x-f[tmp-1]);
}

int main(){
	init();
	int m;
	scanf("%d",&m);
	while(m--){
		ll x,y;
		scanf("%lld%lld",&x,&y);
		printf("%lld\n",query(x,y));
	}
	return 0;
}

参考:
https://www.luogu.org/blog/five20/solution-p3938

于HG机房

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值