C Looooops(信息学奥赛一本通- P1640)

【题目描述】

原题来自:CTU Open 2004

对于 C 语言的

复制代码
for (variable = A; variable != B; variable += C)
  statement;

循环语句,问在 k 位存储系统中循环几次才会结束。若在有限次内结束,则输出循环次数。否则输出死循环。

【输入】

多组数据,每组数据一行四个整数 A,B,C,k。k 表示 k 位存储系统。

读入以0000 结束。

【输出】

若在有限次内结束,则输出循环次数。否则输出 FOREVER。

【输入样例】

复制代码
3 3 2 16
3 7 2 16
7 3 2 16
3 4 2 16
0 0 0 0

【输出样例】

复制代码
0
2
32766
FOREVER

【提示】

数据范围与提示:

对于全部数据,1≤k≤32,0≤A,B,C<2^k 。

1. 题意转换

剥去 C 语言 for 循环的底层执行外衣,这道题本质上是在求解:寻找一元一次同余方程 A+C⋅x≡B(mod 2^k) 的最小非负整数解 x。

2. 思考过程与解题思路

  • 第一直觉的暴力解法 面对这道题,最简单的想法就是"写个程序直接去模拟这个 for 循环":设定一个变量 x = A,每次 x = (x + C) % (1LL << k),同时加一个计数器记录循环次数,直到 x == B 为止;如果跑了一大圈回到了起点还没遇到 B,就输出 FOREVER。 为什么会超时? 题目给定的数据范围中,存储系统位数最大为 k=32。这意味着底层模数 M=2^32≈4×10^9。如果采用暴力步步模拟,最坏情况需要循环 4×10^9 次,这远远超过了竞赛标准 1 秒钟 ∼10^8 次的运算限制,必然会超时。

  • 推导正解:从"步步模拟"到"代数降维" 遇到环形相遇或周期循环问题,必须立刻联想到模运算(同余)。 假设循环执行了 x 次后停止,那么满足的代数条件是:

    A+C⋅x≡B (mod 2^k)

    我们将已知常数移到同侧:

    C⋅x≡B−A (mod 2^k)

    根据同余的定义,这意味着 C⋅x 与 B−A 的差,必定是模数 2^k 的整数倍。假设这个倍数是 y,我们就能写出一个二元一次不定方程:

    C⋅x−2^k⋅y=B−A

    为了方便套用算法模板,写成加法形式也一样:

    C⋅x+2^k⋅y=B−A

    至此,题目被完美转化。已知 C、2^k 和 B−A,求未知数 x 和 y------这就是标准的扩展欧几里得算法裸题。

3. 算法设计与样例推演

  • 核心算法:扩展欧几里得算法

    1. 设模数 m=2^k。我们需要解方程 C⋅x+m⋅y=B−A。

    2. 先呼叫 exgcd(C, m, x0, y0) 求出最大公约数 d=gcd(C,m),以及基础方程 C⋅x0​+m⋅y0​=d 的特解。

    3. 无解判定(裴蜀定理) :如果常数项 (B−A) 不能被 d 整除,说明永远无法到达目标,属于死循环,直接输出 FOREVER。

    4. 解的放大与最小非负化 :真实方程是基础方程的(B−A​)/d倍。我们将 x0​ 乘上这个倍数(times),同时解的真实周期也会随之缩小为 mod = m / d。利用公式 ((x0 * times) % mod + mod) % mod,像海绵一样将 x 压缩转换至最小非负整数。

  • 样例手玩推演(以输入 3 7 2 16 为例): 即 A=3,B=7,C=2,k=16。

    1. 模数 m=2^16=65536。

    2. 方程转化为:2⋅x+65536⋅y=7−3⟹2⋅x+65536⋅y=4。

    3. 调取扩欧求 2 和 65536 的 gcd,得 d=2。基础解 x0​=1,y0​=0 满足 2(1)+65536(0)=2。

    4. 检验裴蜀定理:常数项 4 能被 d=2 整除,有解!

    5. 放大倍数 times = 4 / 2 = 2。

    6. 局部周期 mod = 65536 / 2 = 32768。

    7. 代入转正公式:x=((1×2) (mod 32768)+32768) (mod 32768)=2。

    8. 结果为 2,与样例输出完美吻合。

4. 时空复杂度分析

  • 时间复杂度:O(log(min(C,2^k)))。单次查询的耗时完全取决于扩欧算法中辗转相除的递归深度,呈现严格的对数级别。即使应对极其庞大的多组测试数据,也能在几毫秒内轻松跑完,效率极高。

  • 空间复杂度:O(1)。只用到常数级别的整型变量,递归调用栈最大深度不超过 32 层,不可能触发 MLE。

5. 坑点与易错总结

  1. 致命的 2^64 数据溢出 :当 k=32 时,m=2^32。扩欧算出的基础特解 x0​ 和放大倍数 times 在极限情况下都可能接近 2^32。执行 x0 * times 时,乘积瞬间飙升至 2^64,刚好越过 64 位有符号整型 long long 的极值 (2^63−1),导致算术溢出产生负数乱码。破解法 :强转 __int128(128位超大整型)做乘法护城河。

  2. pow 的浮点精度暗杀 :很多初学者求 2^k 喜欢用 pow(2, k),但 pow 是浮点运算,对于大数极易丢失精度导致末尾数字出错。在数论和位运算中,求 2^k 必须使用 1LL << k。

  3. 移位溢出陷阱 :在写 1 << k 时,如果 k=32,普通的 32 位 1 左移 32 位是 C++ 的未定义行为(UB)。必须在 1 后面加上 LL (即 1LL << k),将基数声明为 64 位整型再移位。

  4. 负数整除与取模 :B−A 完全可能是负数。C++ 的 % 运算对负数处理非常生硬。通过 ((ans % mod) + mod) % mod 这个"黄金转正句型"能彻底规避负数取模带来的隐患。

6. 完整代码

cpp 复制代码
//同余 扩欧
//题目可以转化为一元一次同余方程
//a+c*x0≡b (mod 2^k) (k还需要从位数转化为具体的最大值m)
//x0为循环次数
//可以通过移项继续转化为二元一次不定方程
//c*x0+k*y0=b-a 扩欧求解
#include <iostream>
#include <cmath>
#include <algorithm>
using namespace std;
long long a,b,c,k;
typedef long long ll;

ll exgcd(ll a,ll b,ll &x,ll &y){
    if(b==0){
        x=1;
        y=0;
        return a;
    }
    ll d=exgcd(b,a%b,x,y);
    ll tmp=x;
    x=y;
    y=tmp-(a/b)*y;
    return d;
}

int main(){
    ios::sync_with_stdio(false);
    cin.tie(0);
    while(cin>>a>>b>>c>>k){
        if(a==0&&b==0&&c==0&&k==0) return 0;
        ll x0,y0;
        //默认是有符号整型
        //先求出模数
        //1ll的ll不能丢 防止1在k=32时发生左移溢出
        ll m=1ll<<k;
        //题目转化为c*x0+m*y0=b-a
        //先用扩欧求出方程c*x0+m*y0=gcd(c,m)
        ll d=exgcd(c,m,x0,y0);
        //根据裴蜀定理 无解
        if((b-a)%d!=0){
            cout<<"FOREVER\n";
            continue;
        }
        //算出缩放倍数
        ll times=(b-a)/d;
        //算出新模数 即方程在约分后的真实周期
        ll mod=m/d;

        //算出真正的非负最小循环次数
        //x0和times在极限情况下均可达2^32级别
        //x0*times瞬间可达2^64 刚好撑爆long long 的界限(2^63-1)
        //故在此处强转__int128(128位整型)进行乘法与取模运算 防止数据溢出
        //这里不写__int128也能提交通过 但为了严谨给同学们演示一下用法
        x0=(ll)(((__int128)x0*times)%mod+mod)%mod;
        cout<<x0<<"\n";
    }
    return 0;
}
相关推荐
飘尘3 小时前
从"算子"到"AI Infra":大模型背后看不见的那群人在忙什么
人工智能·算法·面试
夏幻灵4 小时前
每日一题:LeetCode 3:无重复字符的最长子串——滑动窗口思路详解
算法·leetcode·职场和发展
CarIise4 小时前
Java实训阶段查漏补缺复习笔记2
java·linux·算法
无忧.芙桃4 小时前
C++ 并查集详解:原理、实现与应用
c语言·c++·算法·leetcode
蛋蛋的就会顺顺的4 小时前
hot100——二叉树
java·数据结构·算法·leetcode·力扣
Code_Solitude5 小时前
c语言:二分法的完整补充
c语言·开发语言·算法
一切皆是因缘际会5 小时前
掌控信息论:同源星际通信基础理论
人工智能·算法·ai
动词ing7 小时前
【C语言题目练习】算法 整数反转
c语言·开发语言·算法
Nil2087 小时前
leetcode 55跳跃游戏
算法·leetcode·游戏