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 Pr = 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)\)。
算法流程
- 用
Num(q,r)表示当前值模空间的状态。 - 预处理每个数字
c=0..9的倍增表jump[c][j]。jump[c][0]表示x -> 10x + c- 之后不断自我复合
- 对每一段
(c,l):- 把
l按二进制拆分 - 若第
j位为 1,就对当前状态应用一次jump[c][j]
- 把
- 最终状态
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 / D、Div2 F 一档。
难点不在复杂数据结构,而在于:
- 用
(q,r)代替直接维护PM - 把“追加数字”抽象成可倍增的仿射变换

浙公网安备 33010602011771号