competitive/math/
floor_sum.rs

1use super::{
2    AddMulOperation, Associative, BarrettReduction, Group, Invertible, Magma, Monoid, One, Ring,
3    SemiRing, Unital, Wrapping, Zero, array,
4};
5use std::{
6    marker::PhantomData,
7    mem::swap,
8    ops::{Add, Mul, Range},
9};
10
11fn choose2(n: Wrapping<u64>) -> Wrapping<u64> {
12    if n.0 % 2 == 0 {
13        n / 2 * (n - 1)
14    } else {
15        (n - 1) / 2 * n
16    }
17}
18
19/// Sum of Floor of Linear mod 2^64
20///
21/// $$\sum_{i=0}^{n-1}\left\lfloor\frac{a\times i+b}{m}\right\rfloor$$
22pub fn floor_sum(n: u64, a: u64, b: u64, m: u64) -> u64 {
23    let mut ans = Wrapping(0u64);
24    let (mut n, mut m, mut a, mut b) = (Wrapping(n), m, a, b);
25    loop {
26        let br = BarrettReduction::<u64>::new(m);
27        if a >= m {
28            let (q, r) = br.div_rem(a);
29            ans += choose2(n) * q;
30            a = r;
31        }
32        if b >= m {
33            let (q, r) = br.div_rem(b);
34            ans += n * q;
35            b = r;
36        }
37        let y_max = (n * a + b).0;
38        if y_max < m {
39            break;
40        }
41        let (q, r) = br.div_rem(y_max);
42        n = Wrapping(q);
43        b = r;
44        swap(&mut m, &mut a);
45    }
46    ans.0
47}
48
49/// Sum of Floor of Linear mod 2^64
50///
51/// $$\sum_{i=l}^{r-1}\left\lfloor\frac{a\times i+b}{m}\right\rfloor$$
52pub fn floor_sum_i64(l: i64, r: i64, a: i64, b: i64, m: u64) -> i64 {
53    let mut ans = Wrapping(0i64);
54    let (n, m, a, b) = (
55        Wrapping((r - l) as u64),
56        m as i64,
57        a,
58        (Wrapping(l) * a + b).0,
59    );
60    let a = if a < 0 {
61        let r = a.rem_euclid(m);
62        let nc2 = choose2(n);
63        ans -= Wrapping(nc2.0 as _) * ((Wrapping(r) - a) / m);
64        r
65    } else {
66        a
67    };
68    let b = if b < 0 {
69        let r = b.rem_euclid(m);
70        ans -= Wrapping(n.0 as _) * ((Wrapping(r) - b) / m);
71        r
72    } else {
73        b
74    };
75    (ans + floor_sum(n.0, a as u64, b as u64, m as u64) as i64).0
76}
77
78pub fn floor_sum_range_freq(l: i64, r: i64, a: i64, b: i64, m: u64, range: Range<i64>) -> i64 {
79    if range.start >= range.end {
80        return 0;
81    }
82    assert!(0 <= range.start && range.end <= m as i64);
83    let x1 = floor_sum_i64(l, r, a, b - range.start, m);
84    let x2 = floor_sum_i64(l, r, a, b - range.end, m);
85    x1 - x2
86}
87
88struct FloorSum<R, const X: usize, const Y: usize>
89where
90    R: SemiRing,
91{
92    _marker: PhantomData<fn() -> R>,
93}
94
95#[derive(Debug)]
96struct FloorSumData<R, const X: usize, const Y: usize>
97where
98    R: SemiRing,
99{
100    dp: [[R::T; Y]; X],
101    dx: R::T,
102    dy: R::T,
103    _marker: PhantomData<fn() -> R>,
104}
105
106impl<R, const X: usize, const Y: usize> Clone for FloorSumData<R, X, Y>
107where
108    R: SemiRing,
109{
110    fn clone(&self) -> Self {
111        Self {
112            dp: self.dp.clone(),
113            dx: self.dx.clone(),
114            dy: self.dy.clone(),
115            _marker: self._marker,
116        }
117    }
118}
119
120impl<R, const X: usize, const Y: usize> FloorSum<R, X, Y>
121where
122    R: SemiRing,
123{
124    fn to_x() -> FloorSumData<R, X, Y> {
125        let mut dp = array![array![R::zero(); Y]; X];
126        dp[0][0] = R::one();
127        FloorSumData {
128            dp,
129            dx: R::one(),
130            dy: R::zero(),
131            _marker: PhantomData,
132        }
133    }
134    fn to_y() -> FloorSumData<R, X, Y> {
135        FloorSumData {
136            dp: array![array![R::zero(); Y]; X],
137            dx: R::zero(),
138            dy: R::one(),
139            _marker: PhantomData,
140        }
141    }
142}
143
144impl<R, const X: usize, const Y: usize> FloorSum<R, X, Y>
145where
146    R: Ring,
147    R::Additive: Invertible,
148{
149    fn offset(x: i64, y: i64) -> FloorSumData<R, X, Y> {
150        FloorSumData {
151            dp: array![array![R::zero(); Y]; X],
152            dx: R::Additive::signed_pow(R::one(), x),
153            dy: R::Additive::signed_pow(R::one(), y),
154            _marker: PhantomData,
155        }
156    }
157}
158
159impl<R, const X: usize, const Y: usize> Magma for FloorSum<R, X, Y>
160where
161    R: SemiRing,
162{
163    type T = FloorSumData<R, X, Y>;
164
165    fn operate(a: &Self::T, b: &Self::T) -> Self::T {
166        let mut a = a.clone();
167        let mut b = b.clone();
168        let mut pow_x = array![R::zero(); X];
169        let mut pow_y = array![R::zero(); Y];
170        pow_x[0] = R::one();
171        pow_y[0] = R::one();
172        for i in 1..X {
173            pow_x[i] = R::mul(&pow_x[i - 1], &a.dx);
174        }
175        for j in 1..Y {
176            pow_y[j] = R::mul(&pow_y[j - 1], &a.dy);
177        }
178        macro_rules! go {
179            ($N:ident) => {
180                let mut comb = array![array![R::zero(); $N]; $N];
181                comb[0][0] = R::one();
182                let mut i = 0;
183                while i + 1 < $N {
184                    let mut j = 0;
185                    while j <= i {
186                        let x = comb[i][j].clone();
187                        R::add_assign(&mut comb[i + 1][j], &x);
188                        R::add_assign(&mut comb[i + 1][j + 1], &x);
189                        j += 1;
190                    }
191                    i += 1;
192                }
193                for i in 0..X {
194                    for j in (0..Y).rev() {
195                        for k in j + 1..Y {
196                            let mut x = b.dp[i][j].clone();
197                            R::mul_assign(&mut x, &comb[k][j]);
198                            R::mul_assign(&mut x, &pow_y[k - j]);
199                            R::add_assign(&mut b.dp[i][k], &x);
200                        }
201                    }
202                }
203                for j in 0..Y {
204                    for i in (0..X).rev() {
205                        for k in i..X {
206                            let mut x = b.dp[i][j].clone();
207                            R::mul_assign(&mut x, &comb[k][i]);
208                            R::mul_assign(&mut x, &pow_x[k - i]);
209                            R::add_assign(&mut a.dp[k][j], &x);
210                        }
211                    }
212                }
213            };
214        }
215        if X <= Y {
216            go!(Y);
217        } else {
218            go!(X);
219        }
220        R::add_assign(&mut a.dx, &b.dx);
221        R::add_assign(&mut a.dy, &b.dy);
222        a
223    }
224}
225
226impl<R, const X: usize, const Y: usize> Unital for FloorSum<R, X, Y>
227where
228    R: SemiRing,
229{
230    fn unit() -> Self::T {
231        FloorSumData {
232            dp: array![array![R::zero(); Y]; X],
233            dx: R::zero(),
234            dy: R::zero(),
235            _marker: PhantomData,
236        }
237    }
238}
239
240impl<R, const X: usize, const Y: usize> Associative for FloorSum<R, X, Y> where R: SemiRing {}
241
242fn floor_monoid_product<M>(
243    mut x: M::T,
244    mut y: M::T,
245    mut n: u64,
246    mut a: u64,
247    mut b: u64,
248    mut m: u64,
249) -> M::T
250where
251    M: Monoid,
252{
253    let mut c = (a * n + b) / m;
254    let mut pre = M::unit();
255    let mut suf = M::unit();
256    loop {
257        let (p, q) = (a / m, b / m);
258        a %= m;
259        b %= m;
260        x = M::operate(&x, &M::pow(y.clone(), p));
261        pre = M::operate(&pre, &M::pow(y.clone(), q));
262        c -= p * n + q;
263        if c == 0 {
264            break;
265        }
266        let d = (m * c - b - 1) / a + 1;
267        suf = M::operate(&y, &M::operate(&M::pow(x.clone(), n - d), &suf));
268        b = m - b - 1 + a;
269        n = c - 1;
270        c = d;
271        swap(&mut m, &mut a);
272        swap(&mut x, &mut y);
273    }
274    x = M::pow(x.clone(), n);
275    M::operate(&M::operate(&pre, &x), &suf)
276}
277
278/// $$\sum_{i=0}^{n-1}i^X\left\lfloor\frac{a\times i+b}{m}\right\rfloor^Y$$
279pub fn floor_sum_polynomial<T, const X: usize, const Y: usize>(
280    n: u64,
281    a: u64,
282    b: u64,
283    m: u64,
284) -> [[T; Y]; X]
285where
286    T: Clone + Zero + One + Add<Output = T> + Mul<Output = T>,
287{
288    debug_assert!(a == 0 || n < (u64::MAX - b) / a);
289    floor_monoid_product::<FloorSum<AddMulOperation<T>, X, Y>>(
290        FloorSum::<AddMulOperation<T>, X, Y>::to_x(),
291        FloorSum::<AddMulOperation<T>, X, Y>::to_y(),
292        n,
293        a,
294        b,
295        m,
296    )
297    .dp
298}
299
300/// $$\sum_{i=l}^{r-1}i^X\left\lfloor\frac{a\times i+b}{m}\right\rfloor^Y$$
301pub fn floor_sum_polynomial_i64<T, const X: usize, const Y: usize>(
302    l: i64,
303    r: i64,
304    a: i64,
305    b: i64,
306    m: u64,
307) -> [[T; Y]; X]
308where
309    T: Clone + Zero + One + Add<Output = T> + Mul<Output = T>,
310    <AddMulOperation<T> as SemiRing>::Additive: Invertible,
311{
312    assert!(l <= r);
313    assert!(m > 0);
314
315    if a < 0 {
316        let mut ans = floor_sum_polynomial_i64::<T, X, Y>(-r + 1, -l + 1, -a, b, m);
317        for i in (1..X).step_by(2) {
318            for j in 0..Y {
319                ans[i][j] = AddMulOperation::<T>::neg(&ans[i][j]);
320            }
321        }
322        return ans;
323    }
324
325    let add_x = l;
326    let n = (r - l) as u64;
327    let b = a * add_x + b;
328
329    let add_y = b.div_euclid(m as i64);
330    let b = b.rem_euclid(m as i64);
331    assert!(a >= 0);
332    assert!(b >= 0);
333    let data = floor_monoid_product::<FloorSum<AddMulOperation<T>, X, Y>>(
334        FloorSum::<AddMulOperation<T>, X, Y>::to_x(),
335        FloorSum::<AddMulOperation<T>, X, Y>::to_y(),
336        n,
337        a as u64,
338        b as u64,
339        m,
340    );
341
342    let offset = FloorSum::<AddMulOperation<T>, X, Y>::offset(add_x, add_y);
343    FloorSum::<AddMulOperation<T>, X, Y>::operate(&offset, &data).dp
344}
345
346#[cfg(test)]
347mod tests {
348    use super::*;
349    use crate::tools::Xorshift;
350
351    #[test]
352    fn test_floor_sum() {
353        const A: u64 = 1_000;
354        const B: i64 = 1_000;
355        const Q: usize = 1_000;
356        let mut rng = Xorshift::new();
357        for _ in 0..Q {
358            let (n, a, b, m) = rng.random((..A, ..A, ..A, 1..A));
359            let expected: u64 = (0..n).map(|i| (a * i + b) / m).sum();
360            let result = floor_sum(n, a, b, m);
361            assert_eq!(expected, result);
362
363            let (mut l, mut r, a, b) = rng.random((-B..B, -B..B, -B..B, -B..B));
364            if l > r {
365                swap(&mut l, &mut r);
366            }
367            let expected: i64 = (l..r).map(|i| (a * i + b).div_euclid(m as i64)).sum();
368            let result = floor_sum_i64(l, r, a, b, m);
369            assert_eq!(expected, result);
370
371            let (mut lv, mut rv) = rng.random((0..m as i64, 0..m as i64));
372            if lv > rv {
373                swap(&mut lv, &mut rv);
374            }
375            let range = lv..rv + 1;
376            let expected = (l..r)
377                .filter(|&i| range.contains(&(a * i + b).rem_euclid(m as i64)))
378                .count() as i64;
379            let result = floor_sum_range_freq(l, r, a, b, m, range);
380            assert_eq!(expected, result);
381        }
382    }
383
384    #[test]
385    fn test_floor_sum_polynomial() {
386        const P: usize = 3;
387        const A: u64 = 100;
388        const B: i64 = 100;
389        const Q: usize = 1_000;
390        let mut rng = Xorshift::new();
391        for _ in 0..Q {
392            let (n, a, b, m) = rng.random((..A, ..A, ..A, 1..A));
393            let mut expected: [[u64; P]; P] = [[0; P]; P];
394            for (x, expected) in expected.iter_mut().enumerate() {
395                for (y, expected) in expected.iter_mut().enumerate() {
396                    *expected = (0..n)
397                        .map(|i| i.pow(x as u32) * ((a * i + b) / m).pow(y as u32))
398                        .sum();
399                }
400            }
401            let result = floor_sum_polynomial::<u64, P, P>(n, a, b, m);
402            assert_eq!(expected, result);
403
404            let (mut l, mut r, a, b) = rng.random((-B..B, -B..B, -B..B, -B..B));
405            if l > r {
406                swap(&mut l, &mut r);
407            }
408            let mut expected: [[i64; P]; P] = [[0; P]; P];
409            for (x, expected) in expected.iter_mut().enumerate() {
410                for (y, expected) in expected.iter_mut().enumerate() {
411                    *expected = (l..r)
412                        .map(|i| i.pow(x as u32) * (a * i + b).div_euclid(m as i64).pow(y as u32))
413                        .sum();
414                }
415            }
416            let result = floor_sum_polynomial_i64::<i64, P, P>(l, r, a, b, m);
417            assert_eq!(expected, result);
418        }
419    }
420}