Biorhythms(信息学奥赛一本通- P1639)

【题目描述】

原题来自:POJ 1006

人生来就有三个生理周期,分别为体力、感情和智力周期,它们的周期长度为 23 天、28 天和 33 天。每一个周期中有一天是高峰。在高峰这天,人会在相应的方面表现出色。例如,智力周期的高峰,人会思维敏捷,精力容易高度集中。因为三个周期的周长不同,所以通常三个周期的高峰不会落在同一天。对于每个人,我们想知道何时三个高峰落在同一天。对于每个周期,我们会给出从当前年份的第一天开始,到出现高峰的天数(不一定是第一次高峰出现的时间)。

你的任务是给定一个从当年第一天开始数的天数,输出从给定时间开始(不包括给定时间)下一次三个高峰落在同一天的时间(距给定时间的天数)。

例如:给定时间为 10,下次出现三个高峰同天的时间是 12,则输出 2(注意这里不是 3)。

【输入】

本题有多组数据。

对于每组数据,输入四个整数 p,e,i 和 d。p,e,i 分别表示体力、情感和智力高峰出现的时间(时间从当年的第一天开始计算)。d 是给定的时间,可能小于 p,e 或 i。

当 p=e=i=d=−1 时,输入数据结束。

【输出】

从给定时间起,下一次三个高峰同天的时间(距离给定时间的天数)。采用以下格式:

复制代码
Case 1: the next triple peak occurs in 1234 days.

注意:即使结果是 1 天,也使用复数形式 days。

【输入样例】

复制代码
0 0 0 0
0 0 0 100
5 20 34 325
4 5 6 7
283 102 23 320
203 301 203 40
-1 -1 -1 -1

【输出样例】

复制代码
Case 1: the next triple peak occurs in 21252 days.
Case 2: the next triple peak occurs in 21152 days.
Case 3: the next triple peak occurs in 19575 days.
Case 4: the next triple peak occurs in 16994 days.
Case 5: the next triple peak occurs in 8910 days.
Case 6: the next triple peak occurs in 10789 days.

【提示】

数据范围与提示:

所有给定时间是非负的并且小于 365,所求的时间小于 21252。

1. 题意转换

剥去"体力、感情、智力"这层生理周期的外衣,这道题本质上是在求解一个一元线性同余方程组: 已知三个固定周期 m1​=23,m2​=28,m3​=33,以及三个余数 a1​=p,a2​=e,a3​=i。我们需要求出一个天数 x,满足:

x≡p(mod23)

x≡e(mod28)

x≡i(mod33)

在求出满足条件的最小非负整数解 x 后,找到严格大于给定天数 d 的那个解,并输出它们之间的差值 (x−d)。

2. 思考过程与解题思路

  • 第一直觉:暴力跳步法(枚举法) 初学者的直觉通常是:从 d+1 天开始,一天一天往后枚举,判断是不是同时满足上面三个取模条件。稍微聪明一点的暴力是:先找到满足第一个条件的某一天,然后每次加上 23 去找满足第二个条件的,接着再每次加上 23×28 去找第三个。 为什么暴力能过但不优雅? 三个周期的最小公倍数(总周期)是 23×28×33=21252。在最坏情况下,暴力需要循环数万次。虽然 POJ 给的时间限制较宽,暴力不会超时,但如果在高规格比赛中遇到 10^18 级别的模数,暴力肯定不得行。

  • 正解推导:同余方程组的合并 这题是标准得不能再标准的中国剩余定理(CRT)模型,因为模数全部互质。但我们在实际做题时,通常直接套用更具普适性的扩展中国剩余定理(EXCRT),因为这个既可以解决模数两两互质的情况,也可以解决不互质的情况。 面对这三个方程,我们只需要:

    1. 先算出前两个方程的公共解 ans,并求出它们的总周期 lcm。

    2. 拿着这个"半成品"去和第三个方程合并,用扩欧求出跨越的步长。

    3. 合并完毕后,我们就能得到这三者在 21252 天内的唯一共同高峰日。

3. 算法设计与样例推演

  • 核心算法:利用EXCRT迭代合并同余方程。 每轮合并的核心状态转移方程式为:

    lcm⋅x0​+mi⋅y0​=ai−ans

    利用扩欧解出 x0​ 后,真实步数为:

    x0​=(x0​×(ai−ans)/d​) (mod (mi/d)​)

  • 极简数据手玩推演(带入样例 2:p=0, e=0, i=0, d=100): 此时 a={0,0,0,0},m={0,23,28,33}。

    1. 地基初始化 :ans = a[1] = 0,lcm = 23。

    2. 合并第二个方程 (28) : 我们要解 23⋅x0​+28⋅y0​=0−0=0。 显然步长 x0​=0。 更新解:ans = 0 + 0 * 23 = 0,lcm = 23 * 28 = 644。

    3. 合并第三个方程 (33) : 解 644⋅x0​+33⋅y0​=0−0=0。 同样步长 x0​=0。 更新解:ans = 0 + 0 * 644 = 0,lcm = 644 * 33 = 21252。

    4. 尾盘平移处理 : 算出的绝对高峰日是第 0 天。但是题目给定的当前时间是 d=100。 因为要求"下一次"发生的时间,所以我们要加上总周期 21252,直到 ans > 100。 平移后 ans = 21252。 输出答案:21252−100=21152。与样例输出分毫不差。

4. 时空复杂度分析

  • 时间复杂度 :O(T⋅log(max M))。对于每一组输入,只需进行 2 次 exgcd 操作合并三个方程。由于底层全是辗转相除法,单次查询时间极短,相当于 O(1),哪怕有 10^5 组数据也能瞬间解决。

  • 空间复杂度:O(1)。只用到了一维长度为 4 的数组存储模数和余数,内存消耗几乎为零。

5. 坑点与易错总结

  1. 物理概念的张冠李戴 :很多同学读题时,会误把输入的 p,e,i 当作周期去求最小公倍数。记住:23, 28, 33 才是周期(模数),p,e,i 是方程里的余数。

  2. 要求的必须是"下一次" :如果直接输出 ans - d,当算出的大高峰日比今天早(即 ans <= d),就会输出负数。必须通过 while(ans <= d) ans += lcm; 将天数推移到未来。

  3. 负数整除与黄金转正的奇妙碰撞 :在代码 times = (a[i] - ans) / dd 中,由于 23,28,33 两两互质,最大公约数 dd 永远为 1。虽然 a[i] - ans 是负数,但负数除以 1 没有任何精度截断误差。随后一句 x0 = ((x0 * times) % mod + mod) % mod; 完美吸收了所有负数带来的偏移,逻辑自洽,非常漂亮。

  4. 多组数据的输出格式 :不要漏掉输出格式里的 Case X: ,且无论天数多少,结尾一律使用复数 days.。

6. 完整代码

cpp 复制代码
//这道题是典型的扩展中国剩余定理的应用
#include <iostream>
using namespace std;
long long p,e,i,d;
int idx;//记录当前是第几组测试数组
typedef long long ll;
ll m[4]={0,23,28,33};//周期 即模数
ll a[4];//对应每个周期出现高峰的日子 即余数 对应p e i

//扩展中国剩余定理
ll exgcd(ll a,ll b,ll &x_,ll &y_){
    if(b==0){
        x_=1;
        y_=0;
        return a;
    }
    ll dd=exgcd(b,a%b,x_,y_);
    ll tmp=x_;
    x_=y_;
    y_=tmp-(a/b)*y_;
    return dd;
}

//扩展中国剩余定理求解
ll excrt(){
    //先把第一个方程的解当作地基
    //ans代表当前已经计算出来的答案
    ll ans=a[1];
    //lcm代表当前所有已合并方程的周期的最小公倍数
    //即联合大周期
    ll lcm=m[1];

    //遍历所有方程
    for(int i=2;i<=3;i++){
        //先明确当前第i轮的目标方程
        //ans+x0*lcm=a[i] (mod m[i])
        //转化可以得到方程
        //lcm*x0+m[i]*y0=a[i]-ans
        ll x0,y0;
        //扩欧求解
        ll dd=exgcd(lcm,m[i],x0,y0);
        //根据裴蜀定理 无解情况 但由题目可以知道
        //应该不会出现此情况
        if((a[i]-ans)%dd!=0) return -1;

        //计算缩放倍数
        ll times=(a[i]-ans)/dd;

        //计算新模数
        ll mod=m[i]/dd;

        //计算
        x0=((x0*times)%mod+mod)%mod;

        //计算新的ans 即满足当前所有方程的解
        ans=ans+x0*lcm;

        //计算新lcm 即新的全局周期
        lcm=lcm*m[i]/dd; 

        //计算非负最小ans
        ans=(ans%lcm+lcm)%lcm;
    }
    //如果给定的时间在三个高峰同一天之前 
    if(d<ans) return ans-d;
    //如果给定时间在三个高峰同一天之后
    else{
        //从三个高峰同一天不断往后推移一个周期
        while(ans<=d) ans=ans+lcm;
        return ans-d;
    }
}



int main(){
    ios::sync_with_stdio(false);
    cin.tie(0);
    //多组数据输入
    while(cin>>p>>e>>i>>d){
        if(p==-1&&e==-1&&i==-1&&d==-1) return 0;
        a[1]=p;
        a[2]=e;
        a[3]=i;
        idx++;
        cout<<"Case "<<idx<<": the next triple peak occurs in "<<excrt()<<" days.\n";
    }
    return 0;
}
相关推荐
奋发向前wcx6 小时前
CSP-J复赛模拟赛4 王晨旭补题 2026.10.5
算法
念何架构之路9 小时前
zap采样器与性能优化内幕
算法·性能优化·哈希算法
晒太羊的猫10 小时前
hot100动态规划刷题
算法·动态规划
weixin_5310946910 小时前
heap_4内存管理
java·开发语言·算法
余额瞒着我当琳10 小时前
红黑树的实现--红黑树的概念、规则、效率、结构、旋转、查找、验证
数据结构·c++·算法
程序员南飞10 小时前
win系统安装minio
java·数据结构·算法
青少儿编程课堂10 小时前
AC自动机多模式串匹配精讲:校园关键词巡检实战解析
c++·python·算法·bfs·信息学竞赛
longlongzihan11 小时前
LeetCode 5. 最长回文子串:从暴力匹配到中心扩展的思维演进
c++·算法·leetcode
waoooqwe11 小时前
问卷样本真实吗
大数据·数据库·算法·数据分析