1 条题解

  • 0
    @ 2026-5-8 17:25:36

    1. 题意翻译

    有一个区间 ([l, r]),长度 (len = r - l)。可以进行两种操作:

    1. LTR (left to right):选择 (x) 满足 (l < x \le r),且 (l + x) 是质数,且 选最大的可能 x
      折叠后新区间为
      [ \left[ \frac{l+x}{2}, r \right] ] 注意新左端点是中点 ( (l+x)/2 ),右端点仍是 (r)。

    2. RTL (right to left):选择 (x) 满足 (l \le x < r),且 (r + x) 是质数,且 选最小的可能 x
      折叠后新区间为
      [ \left[ l, \frac{r+x}{2} \right] ] 右端点是中点 ( (r+x)/2 ),左端点仍是 (l)。

    选择的规则:选 最大 x(LTR)或 最小 x(RTL)且使 (l+x) 或 (r+x) 是质数。
    每次操作后区间长度减少;目标是获得不能再缩的 最短长度,并计数所有能达到该最短长度的折叠序列数。


    2. 关键点

    (1) 折叠后区间的性质

    折叠后,一个端点变为中点,另一端不变 → 总长度减少至少一半 吗?
    不一定,取决于 (x) 的位置:

    • LTR:新左端为 ((l+x)/2 > l),长度 = (r - (l+x)/2 = (r-l) + (l - x)/2)... 等一下,我们仔细算:

      新旧长度: [ \text{old len} = r - l ] [ \text{new len} = r - \frac{l+x}{2} = r - \frac{l}{2} - \frac{x}{2} ] 还没化简清楚。还是用简单例子:
      设 (l=1,r=9) → LTR: 选 (x) 使得 (1+x) 质数,且最大可能。
      1+9=10不是质数,1+8=9不是质数,1+7=8不是,1+6=7质数,最大x 满足是 x=6?
      注意 x 可以到 r=9,检查 9: 1+9=10不是质数,x=8:1+8=9不是,x=7:8不是,x=6:7质数 → 所以选 x=6。
      新区间[(1+6)/2, r] = [3.5, 9] 长度=5.5。老长度=8。
      不太直观,但长度变化不用太担心具体值,关键是想 折叠次数有限,最终长度依赖于起点和终点附近质数关系

    但这里有陷阱:
    l+x是质数,x 最大时,不一定 x=r,而是最大值 ≤ r 使 l+x 质数。
    所以其实 x 由 l 决定,只要 r 足够大就总是 r (若 l+r 质数)?

    仔细核对公式:LTR: 选 x 使得 l+x = 质数,x 最大 ≤ r。所以 x = min(r, 最大 t 使得 l+t 质数且 t ≤ r)。
    r 一定 ≥ x,所以新左端= (l+x)/2,右=r,所以长度新 = r-(l+x)/2。不会影响,因为 x 由 l 决定。

    同样 RTL: 选 x 使得 r+x 质数且 x最小 ≥ l。 所以 x = max(l, 最小 t 使得 r+t 质数且 t ≥ l)。
    新右端= (r+x)/2,左=l,长度新 = (r+x)/2 - l。


    (2)最终区间的长度

    不能继续折叠的条件:

    • LTR 不能操作:要么 (l+x) 不是质数对所有 (x\in(l,r]),意味着 (l+1) 不是质数,l+2 不是质数... l+r 不是质数。因为 r 可达任意大,实际是:所有质数 p ≥ l+2?不对,x 可以为 l+1 吗? x>l。最小 x=l+1。所以 l+x 的最小值为 2l+1。但这不是重点,重点是存在 x 让 l+x 质数是大概率,除非 l=1 有些情况,但1随便。但条件是 l+ x 质数。

    同样 RTL: 不能操作 ⇔ 对所有 x ∈ [l, r-1],r+x 不是质数。即通过不同 x 无法让 r+x 是质数。

    最终折不动的时候,区间极小。题目例子 [1,30] 最终长度 1。


    (3)确定性选择

    因为折叠规则是 fixed:LTR选最大 x,RTL 选最小 x。所以从一个状态 (l,r) 出发,操作是确定性的。

    也就是说,若 (l,r) 可以 LTR,就 LTR 并且得到唯一的下一个区间;若可以 RTL 就 RTL,唯一的下一个;若两者都可,则有两种不同的选择。

    所以折叠图是一个有向树/图,从初始点走向无法折叠的端点。多条可能的序列对应不同的选择点。


    (4)问题转化为图路径计数

    设一个状态 (l,r) 当长度 (len) 已经是最小的不能再折时,停止。

    目标:所有可能折叠序列 -> 最终短长度 min_len → 计数从初始 (l,r) 到某个最终 min_len 状态的路径数(有限步)。

    因为操作确定,我们可以建个 DAG:每个 (l,r) → 下一个状态(如果有操作)。


    3. 运算复杂度

    r-l ≤ 1e5,即长度 O(1e5),那么最多折叠多少次?
    长度会减少,但不是每次减半,可能只减一点点。也许有贪心可以保证长度至少减到原一半以下?如果一直能折,长度可能很快到 1 或 0。但我们只有 1e5 长度,那么折 1e5 步(手工不算太大)。但 r 可到 1e12,但我们只关心相对位置,可以只处理区间长度范围 ≤1e5 的差分。

    关键:
    折叠后左端点或右端点变化,但它不一定是整数(/2 可能分数),但长度一直减少,并且最终长度应该是 0? 不对,例子最终长度 1,所以终点长度固定?但长度只能是整数吗?l,r 整数,但中间 (l+x)/2 可能是分数,长度是整数差末?

    等一下,(l+x)/2 可能不是整数,所以 l 可能变成非整数?那么 l 和 r 可能是半整数?比如说 l=1, r=9,选 x=6,新区间 [3.5, 9],以后可能还能折,因为 r 是整数 9, 新 l=3.5,长度变 5.5。所以状态会包括半整数。

    但 x 选择满足 l+x 质数,x 整数,所以 (l+x)/2 半整数。

    即 l 和 r 可能变成 .5 整数 + 0.5 。比如说 3.5 = 35/10 就麻烦了 → 实际上 (l+x)/2 分母 2。所以端点可能是 k/2 形式。

    但输入 l,r 是整数,可能 2 次操作后出现 n/4 吗?不会,因为 l 和 x 相加是偶数(质数是奇数?不,2 是质数,那 1+2=3 偶数?1+1=2 质数,但 2 偶数,所以 l+x 可偶数 2 吗?x=1,但 x>l 对 LTR?不对,l=1 时最小 l+1=2 质数,x=1 不满足 x>l,所以无 LTR。所以 x>l 下 l+x > 2l > 2,所以 l+x 偶质数只有 2 不可能。所以 l+x 奇质数,奇数+奇数=偶?no,l整数,x整数,l+x 奇质数意味着奇偶不同→所以 l 和 x 奇偶不同 → (l+x)/2 整数 或 .5?奇+偶=奇,/2 是 k+0.5。

    所以左边端点形式 k+0.5 或 k。

    右边可类似看出 (r+x)/2 可能是整数或半整数。

    但这样状态多,但长度 r-l 依旧是半整数或整数,但具体可以维护。但总步数额度 O(长度) ≤ 1e5,所以可以编码。


    4. 记忆化搜索

    我们直接状态 (a,b) 表示 a,b 为左、右端点,长度 d = b-a。
    d 会一直减少直到无法折。

    每次从 (a,b) 出发:

    • 可 LTR 吗?找到最大 x 在 (a+1 .. b] 使得 a+x 质数。最大设为 xL。
    • 可 RTL 吗?找到最小 x 在 [a .. b-1] 使得 b+x 质数。最小设为 xR。

    xL 寻找:从 x=b 向下到 a+1,找 a+x 质数。(小优化:质数表) xR 寻找:从 x=a 向上到 b-1,找 b+x 质数。

    如果可 LTR: 下一步 = ( (a+xL)/2, b ) 如果可 RTL: 下一步 = ( a, (b+xR)/2 )

    DP 求最短最终长度 minLen, 并计数。


    5. 质数判断

    由于 a+x 可能大到 2e12,不能筛到 2e12,但 b-a ≤ 1e5,所以 x 范围长度 1e5,所以 a+x 与 b+x 离 a,b 在 1e5 内。我们判断质数可用 Miller-Rabin 判断这些 a+x 和 b+x。

    但注意 a+x 的 x∈(a, b] 即 a+x∈(2a, a+b],大概是 [2l, l+r] 这些值。
    r-l ≤ 1e5 → l+r ≤ 2l+1e5。

    但 l 可达 1e12,所以 2l ~ 2e12,没法筛。只能 MR。


    6. 实现思路

    1. 用 Miller Rabin + 确定性基(可以做到 1e18 准确)。
    2. 用 pair<double,double> 或 分数结构 (numerator/denom) 表示状态,因为分母只能是 1 或 2。
    3. BFS/记忆化搜索,从初始 (l,r) 开始,DP 返回 (minLen, waysMod)。
    4. 最终若无法再折时,长度为 d 不可再减少,存 d 作为 minLen 并返回。

    注意长度比较用 double 可能会浮点误差,最好用分数 (p/q),q=1 or 2。


    7. 样例验证

    例子 [1,30],可 LTR: 选最大 x 使 1+x 质数。
    1+30=31 质数,选 x=30,(31/2,30) = (15.5, 30)
    也可以 RTL: 最小 x 使 30+x 质数,30+2=32 非质,30+3=33非,30+4=34非,... 30+11=41 质数,所以 x=11。新区间 (1, (30+11)/2)=(1,20.5)。

    再继续:这样最终可能走不同路径到 [n, n+1] 或 [m, m+1] 长度 1。

    count 结果是 3,示例一致。


    8. 写代码

    我们可以用 map<pair<ll,ll>,pair<ll,ll>> 存 (l, r) → (minLen, count) 结果。
    由于长度 ≤1e5,总状态 O(1e5) 级。

    我会按上述思路实现:

    #include <bits/stdc++.h>
    using namespace std;
    using ll = long long;
    const int MOD = 998244353;
    
    // Miller-Rabin 素数判定 (确定性基 for 64 位)
    bool isPrime(ll n) {
        if (n < 2) return false;
        if (n % 2 == 0) return n == 2;
        
        ll d = n - 1;
        int s = 0;
        while (d % 2 == 0) {
            d /= 2;
            s++;
        }
        
        vector<ll> bases;
        if (n < 341550071728321LL) {
            bases = {2, 3, 5, 7, 11, 13, 17};
        } else {
            bases = {2, 325, 9375, 28178, 450775, 9780504, 1795265022};
        }
        
        for (ll a : bases) {
            if (a % n == 0) continue;
            ll x = 1, y = a;
            ll rem = d;
            while (rem > 0) {
                if (rem & 1) x = (__int128)x * y % n;
                y = (__int128)y * y % n;
                rem >>= 1;
            }
            if (x == 1 || x == n - 1) continue;
            bool composite = true;
            for (int r = 1; r < s; r++) {
                x = (__int128)x * x % n;
                if (x == n - 1) {
                    composite = false;
                    break;
                }
            }
            if (composite) return false;
        }
        return true;
    }
    
    // 表示分数形式,分母 1 or 2
    struct Frac {
        ll num, den; // den = 1 or 2
        Frac(ll n, ll d) : num(n), den(d) {
            if (den == 2 && (num % 2 == 0)) {
                num /= 2;
                den = 1;
            }
            if (den == 2 && (num % 2)) {
                // keep fraction
            }
        }
        bool operator<(const Frac& o) const {
            return num * o.den < o.num * den;
        }
        bool operator==(const Frac& o) const {
            return num * o.den == o.num * den;
        }
        Frac operator+(const Frac& o) const {
            ll n = num * o.den + o.num * den;
            ll d = den * o.den;
            // reduce
            if (d == 2 && n % 2 == 0) {
                n /= 2;
                d = 1;
            }
            return Frac(n, d);
        }
    };
    
    map<pair<Frac,Frac>, pair<ll,ll>> memo; // {(l,r)} -> {minLen, ways}
    
    ll gcd(ll a, ll b) { return b ? gcd(b, a % b) : a; }
    
    pair<ll,ll> solve(Frac l, Frac r) {
        if (memo.count({l, r})) return memo[{l, r}];
        
        ll len_num = r.num * l.den - l.num * r.den;
        // len = len_num / (l.den * r.den)
        // not needed exact, just compare
        
        // 检查能否 LTR
        bool canLTR = false, canRTL = false;
        Frac nextLTR_l, nextRTL_r;
        
        // LTR: 找最大 x s.t. l + x 质数
        // x 整数, x > l, x <= r
        Frac startX = l + Frac(1,1);
        if (startX < r) {
            for (ll x = floor(r.num / r.den); x > floor((l.num + l.den-1)/l.den); x--) {
                // a = l.num/l.den, we need a+x prime?
                ll sum_num = l.num * 1 + x * l.den;
                ll sum_den = l.den;
                // sum = sum_num / sum_den
                // need integer? Since a is half-int possible, but sum could be half-int -> wait x integer, l half-int.
                // a+x = (l.num + 2l.den? no)
                // a = nl/2, x integer, a+x = (nl+2x)/2, integer iff nl+2x even.
                // easier: compute value as double or exact.
                // But a half-int + int half-int or integer? Let's do exact fraction.
                ll num_sum = l.num + x * l.den;
                ll den_sum = l.den;
                // reduce fraction to check if integer prime
                if (num_sum % den_sum == 0) {
                    ll t = num_sum / den_sum;
                    if (isPrime(t)) {
                        canLTR = true;
                        Frac newL((l.num + x * l.den) / (2 * l.den), 1);
                        nextLTR_l = newL;
                        // r unchanged
                        break;
                    }
                }
            }
        }
        
        // RTL: 找最小 x s.t. r + x 质数
        // x 整数, x >= l, x < r
        Frac endX = r - Frac(1,1);
        if (l < endX) {
            for (ll x = ceil(l.num / l.den); x < floor(r.num / r.den); x++) {
                // r + x 质数?
                ll num_sum = r.num + x * r.den;
                ll den_sum = r.den;
                if (num_sum % den_sum == 0) {
                    ll t = num_sum / den_sum;
                    if (isPrime(t)) {
                        canRTL = true;
                        Frac newR((r.num + x * r.den) / (2 * r.den), 1);
                        nextRTL_r = newR;
                        break;
                    }
                }
            }
        }
        
        if (!canLTR && !canRTL) {
            // 终点
            return memo[{l, r}] = {len_num, 1};
        }
        
        vector<pair<ll,ll>> childRes;
        if (canLTR) childRes.push_back(solve(nextLTR_l, r));
        if (canRTL) childRes.push_back(solve(l, nextRTL_r));
        
        ll minLen = LLONG_MAX, ways = 0;
        for (auto [ml, w] : childRes) {
            if (ml < minLen) {
                minLen = ml;
                ways = w;
            } else if (ml == minLen) {
                ways = (ways + w) % MOD;
            }
        }
        return memo[{l, r}] = {minLen, ways};
    }
    
    int main() {
        ios_base::sync_with_stdio(false);
        cin.tie(nullptr);
        
        int t;
        cin >> t;
        while (t--) {
            ll L, R;
            cin >> L >> R;
            memo.clear();
            auto res = solve(Frac(L, 1), Frac(R, 1));
            cout << res.second % MOD << "\n";
        }
        
        return 0;
    }
    

    注意:

    • Miller-Rabin 处理所有质数判断。
    • 用分数结构存储端点,分母 1 或 2。
    • 每一步暴力找最大/最小 x 来满足质数条件。
    • 1

    信息

    ID
    7013
    时间
    1000ms
    内存
    256MiB
    难度
    7
    标签
    递交数
    4
    已通过
    1
    上传者