【题解】斐波拉契 luogu3938

本文介绍了一道关于斐波那契数列和兔子繁殖的数学问题,小C想要找出兔子间的最近公共祖先。解决方案包括针对70%数据的模拟法和100%数据的二分搜索法,通过模拟或二分查找计算兔子的最近公共祖先。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

题目

题目描述

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

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

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

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

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

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

输入输出格式

输入格式:

从标准输入读入数据。 输入第一行,包含一个正整数 m。 输入接下来 m 行,每行包含 2 个正整数,表示 aia_iai​ 和 bib_ibi​ 。

输出格式:

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

输入输出样例

输入

5 
1 1 
2 3 
5 7 
7 13 
4 12

输出

1 
1 
2 
2 
4

说明

题解

分析

首先考虑70%的数据,

每天新出生的兔子数目一定是f[i],这个很容易计算得出

然后发现,这f[i]只兔子的父亲一定是1~f[i],于是模拟这个过程,做一遍LCA即可

再考虑100%的数据,

n达到int以上,无法模拟,

s[i]=∑f[1~i],发现第s[i]+1只兔子父亲肯定是1,第s[i]+2只兔子父亲肯定是2,第s[i]+f[i+1]只兔子父亲一定是f[i+1]

于是有思路:二分s数组,使得s[i]+1<=a<=s[i]+f[i+1],这时候a的父亲就是a-s[i]

这样的话一开始a是大于s[i]的,减过之后就小于s[i]了,至少折半,效率至多是O(logn),感觉挺快的

对b也做一遍,记录他们的“祖先历程”,然后用两个指针找一下公共祖先就可以了

实际上我是处理到10^6的,如果小于10^6就直接做倍增了,这样可能快一些,不过事实证明是差不多的

代码

#include<cstdio>
#include<algorithm>
#include<cstring>
#include<iostream>
#include<vector>
#define ll long long
#define maxn 1000005
#define lo 21
using namespace std;

inline ll read() {
    ll x=0,w=1;
    char ch=getchar();
    while(ch!='-'&&(ch<'-'||ch>'9')) ch=getchar();
    if(ch=='-') w=-1,ch=getchar();
    while(ch>='0'&&ch<='9') x=(x<<3)+(x<<1)+ch-48,ch=getchar();
    return x*w;
}

int fa[lo][maxn];
ll M=1e12+50;
ll d[maxn];
ll f[maxn],sum[maxn];
vector<ll> fath[2];

int lca(int,int);
void work(ll,int);

int main() {
    freopen("fibonacci.in","r",stdin);
    freopen("fibonacci.out","w",stdout);
    f[0]=f[1]=f[2]=1;
    sum[0]=1;sum[1]=2;sum[2]=3;
    fa[0][2]=fa[0][3]=1;
    d[2]=d[3]=1;
    register int i=3,j=0;

    while(1) 
    {
        f[i]=f[i-1]+f[i-2];
        sum[i]=sum[i-1]+f[i];
        if(sum[i]>M) break;
        i++;
    }
    i=3;
    while(1) 
    {
        for(j=sum[i-1]+1; j<=sum[i]&&j<maxn; j++) 
        {
            fa[0][j]=j-sum[i-1];               /*根据规律找父亲节点*/
            d[j]=d[j-sum[i-1]]+1;
        }

        if(j>=maxn) break;
        i++;
    }
    for(int k=1; k<lo; k++) for(int i=1; i<maxn; i++) fa[k][i]=fa[k-1][fa[k-1][i]];            
    int T=read();

    for(register int i=1;i<=T;++i)
    {
        ll a=read(),b=read();
        if(a==b) 
        {
            printf("%lld\n",a);
            continue;
        }

        if(a<maxn&&b<maxn)    /*小于1e6的数据直接做lca*/
        {
            printf("%d\n",lca(a,b));
        } 
        else 
        {
            fath[0].clear(),fath[1].clear();
            work(a,0);
            work(b,1);
            int i=0,j=0,flag=0;

            while(i<fath[0].size()&&j<fath[1].size())   /*lca*/
            {
                if(fath[0][i]==fath[1][j]) 
                {
                    printf("%lld\n",fath[1][j]);
                    flag=1;
                    break;
                }
                if(fath[0][i]>fath[1][j]) i++;
                else j++;
            }

            if(!flag) 
            {
                a=fath[0][fath[0].size()-1],b=fath[1][fath[1].size()-1];
                printf("%d\n",lca(a,b));
            }
        }
    }
    return 0;
}

int lca(int x,int y)   
{
    if(d[x]<d[y]) swap(x,y);
    for(register int k=d[x]-d[y],p=0; k; p++,k>>=1) if(k&1) x=fa[p][x];
    if(x==y) return x;
    for(register int k=lo-1; k>=0; k--) if(fa[k][x]!=fa[k][y]) x=fa[k][x],y=fa[k][y];
    return fa[0][x];
}

void work(ll a,int t)    /*对于大于1e6的数据找祖先*/
{
    ll x=a;
    fath[t].push_back(x);
    while(1) 
    {
        if(x<maxn) break;
        int L=0,R=60;

        while(R-L>1) 
        {
            int mid=(L+R)/2;
            if(sum[mid]<x) L=mid;
            else R=mid;
        }

        if(sum[R]<x) x=x-sum[R];
        else x=x-sum[L];
        fath[t].push_back(x);
    }
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值