U667228 Simple Division(Hard)解题报告

U667228 Simple Division(Hard)解题报告

原题链接

题意简述

给定若干段 \((c_i, l_i)\),表示把数字 \(c_i\) 连写 \(l_i\) 次,拼成一个极长十进制数 \(X\)
要求计算:

\([ \left\lfloor \frac{X}{M} \right\rfloor \bmod P ]\)

其中 \(X\) 无法直接存储。


关键观察1:对于除以M操作,我们可以直接维护两个值整除值 \(q\),余数值 \(r\)

\([ X= qM + r ]\)

\([ q=\left\lfloor \frac{X}{M} \right\rfloor \bmod P ]\)

\([ r={X} \bmod {M} ]\)

struct Num {
    // x ≡ q * M + r 
    // 其中 0 <= q < P, 0 <= r < M
    u64 q, r;
};

也就是维护:

  • q = floor(x / M) mod P
  • r = x mod M

若强行把[q,r]绑定在一个模空间意义下显然模数是MP,但若是分开维护递推只需要维护各自模空间\([q \bmod P,r \bmod M]\)的结果即可

下定义Num{q,r}意义下的加法和乘法操作

1. 加法

\([ x = q_1M + r_1,\quad y = q_2M + r_2 ]\)

\([ x+y = (q_1+q_2+\lfloor (r_1+r_2)/M \rfloor)M + ((r_1+r_2)\bmod M) ]\)

显然加法后仍满足只需要维护Num{q,r}系数的条件。

2. 乘法

同样设

\([ x = q_1M+r_1,\quad y = q_2M+r_2 ]\)

相乘得到

\([ q_{next} = q_1q_2M + q_1r_2 + q_2r_1 + \left\lfloor \frac{r_1r_2}{M} \right\rfloor ]\)

\([ r_{next} = (r_1r_2 \bmod M) ]\)

于是有

\([ xy = M\Big(q_1q_2M + q_1r_2 + q_2r_1 + \left\lfloor \frac{r_1r_2}{M} \right\rfloor\Big) + (r_1r_2 \bmod M) ]\)

如此Num{r,q}的加法操作和乘法操作定义完备


关键观察2:往十进制末尾追加一个数字 c,数值变化为:

\([ x := 10x+c ]\)

这是一个标准的仿射变换
其显然满足结合律的性质,下证:
\([ f(x)=ax+b ]\)

复合公式

\([ f(x)=a_1x+b_1,\qquad g(x)=a_2x+b_2 ]\)

\([ g(f(x)) = (a_2a_1)x + (a_2b_1+b_2) \)]

所以仿射变换满足结合律基本性质。
现定义$ g(f(x)) $复合符号为 $ f\circ g$


倍增性质

以上得出满足结合律

\([ (h\circ g)\circ f = h\circ (g\circ f) ]\)

因此,对于“连续追加同一个数字 d”这种具有结合律的操作,可以通过像快速幂一样预处理:

  • \(jump_{c,0}\):追加 \(c\) 一次
  • \(jump_{c,1}\):追加 \(c\) 两次
  • \(jump_{c,2}\):追加 \(c\) 四次
  • ...

即:

\([ jump_{c,i+1} = jump_{c,i} \circ jump_{c,i} ]\)

这样就能在 (O(\log l)) 时间内处理一段 \((c,l)\)


算法流程

  1. Num(q,r) 表示当前值模空间的状态。
  2. 预处理每个数字 c=0..9 的倍增表 jump[c][j]
    • jump[c][0] 表示 x -> 10x + c
    • 之后不断自我复合
  3. 对每一段 (c,l)
    • l 按二进制拆分
    • 若第 j 位为 1,就对当前状态应用一次 jump[c][j]
  4. 最终状态 Num(q,r) 中的 q 即答案。

复杂度

\(LOG = 61\)

  • 预处理:\((O(10 \cdot LOG))\)
  • 每段处理:\((O(LOG))\)
  • 总复杂度:

\([ O(K \log 10^{18}) = O(61K) ]\)

  • 空间复杂度:\((O(10 \cdot LOG))\)

参考代码

#include <bits/stdc++.h>
using namespace std;
using u64 = uint64_t;
using u128 = __uint128_t;
constexpr int LOG = 61;
u64 M, P, l;
inline u64 Mul(u64 a, u64 b) {
    return (u64)((u128)a * b % P);
}//防爆乘
struct Num {
    u64 q, r;
    Num(){};
    Num(u64 q,u64 r):q(q),r(r){}
    static Num Modify(u64 x) {
        return Num((x / M) % P, x % M);
    }
    Num operator + (const Num& o) const {
        u128 s = (u128)r + o.r;
        return Num(((u128)q + o.q + s / M) % P,s % M);
    }
    Num operator * (const Num& o) const {
        u128 t = (u128)r * o.r;
        u64 nr = (u64)(t % M);
        u64 c = (u64)(t / M);
        u64 x = r % P, y = o.r % P;
        u64 nq = ((u128)Mul(Mul(q, o.q), M%P) +Mul(q, y) +Mul(x, o.q) +c % P) % P;
        return Num(nq, nr);
    }
};//重载余数类
struct F {
    Num a, b;
    F(){};
    F(Num a,Num b):a(a),b(b){};
    Num operator () (const Num& x) const {
        return a*x+b;
    }
    F operator * (const F& o) const {
        return F(a*o.a, a*o.b+b);
    }
};//重载仿射变化类
int main() {
    ios::sync_with_stdio(false);
    cin.tie(nullptr);
    int k,c;
    cin>>k>>M>>P;
    array<array<F, LOG>, 10> jump{};
    Num ten=Num::Modify(10);
    for (int d = 0; d < 10; d++) {
        jump[d][0] = {ten, Num::Modify(d)};
        for (int i = 1; i <LOG ; i++) {
            jump[d][i]=jump[d][i - 1]*jump[d][i - 1];
        }
    }
    Num res=Num::Modify(0);//相当于快速幂中的单位元
    while(k--){
        cin>>c>>l;
        int x=0;
        while(l>0){
            if(l&1){
                res=jump[c][x](res);
            }
            ++x;
            l>>=1;
        }
    }
    cout<<res.q<<'\n';
    return 0;
}

Tag

  • 数学
  • 仿射变换
  • 倍增 / 二进制拆分
  • 自定义模运算
  • 大整数规避 / 实现

难度评价

这题如果放在 Codeforces,体感大约在:

\( \boxed{2400 \sim 2600} \)

大致是 Div1 C / DDiv2 F 一档。
难点不在复杂数据结构,而在于:

  1. (q,r) 代替直接维护 PM
  2. 把“追加数字”抽象成可倍增的仿射变换
posted @ 2026-03-08 21:27  usedchang  阅读(0)  评论(0)    收藏  举报