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.is_multiple_of(2) {
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<Additive: Invertible>,
147{
148    fn offset(x: i64, y: i64) -> FloorSumData<R, X, Y> {
149        FloorSumData {
150            dp: array![array![R::zero(); Y]; X],
151            dx: R::Additive::signed_pow(R::one(), x),
152            dy: R::Additive::signed_pow(R::one(), y),
153            _marker: PhantomData,
154        }
155    }
156}
157
158impl<R, const X: usize, const Y: usize> Magma for FloorSum<R, X, Y>
159where
160    R: SemiRing,
161{
162    type T = FloorSumData<R, X, Y>;
163
164    fn operate(a: &Self::T, b: &Self::T) -> Self::T {
165        let mut a = a.clone();
166        let mut b = b.clone();
167        let mut pow_x = array![R::zero(); X];
168        let mut pow_y = array![R::zero(); Y];
169        pow_x[0] = R::one();
170        pow_y[0] = R::one();
171        for i in 1..X {
172            pow_x[i] = R::mul(&pow_x[i - 1], &a.dx);
173        }
174        for j in 1..Y {
175            pow_y[j] = R::mul(&pow_y[j - 1], &a.dy);
176        }
177        macro_rules! go {
178            ($N:ident) => {
179                let mut comb = array![array![R::zero(); $N]; $N];
180                comb[0][0] = R::one();
181                let mut i = 0;
182                while i + 1 < $N {
183                    let mut j = 0;
184                    while j <= i {
185                        let x = comb[i][j].clone();
186                        R::add_assign(&mut comb[i + 1][j], &x);
187                        R::add_assign(&mut comb[i + 1][j + 1], &x);
188                        j += 1;
189                    }
190                    i += 1;
191                }
192                for i in 0..X {
193                    for j in (0..Y).rev() {
194                        for k in j + 1..Y {
195                            let mut x = b.dp[i][j].clone();
196                            R::mul_assign(&mut x, &comb[k][j]);
197                            R::mul_assign(&mut x, &pow_y[k - j]);
198                            R::add_assign(&mut b.dp[i][k], &x);
199                        }
200                    }
201                }
202                for j in 0..Y {
203                    for i in (0..X).rev() {
204                        for k in i..X {
205                            let mut x = b.dp[i][j].clone();
206                            R::mul_assign(&mut x, &comb[k][i]);
207                            R::mul_assign(&mut x, &pow_x[k - i]);
208                            R::add_assign(&mut a.dp[k][j], &x);
209                        }
210                    }
211                }
212            };
213        }
214        if X <= Y {
215            go!(Y);
216        } else {
217            go!(X);
218        }
219        R::add_assign(&mut a.dx, &b.dx);
220        R::add_assign(&mut a.dy, &b.dy);
221        a
222    }
223}
224
225impl<R, const X: usize, const Y: usize> Unital for FloorSum<R, X, Y>
226where
227    R: SemiRing,
228{
229    fn unit() -> Self::T {
230        FloorSumData {
231            dp: array![array![R::zero(); Y]; X],
232            dx: R::zero(),
233            dy: R::zero(),
234            _marker: PhantomData,
235        }
236    }
237}
238
239impl<R, const X: usize, const Y: usize> Associative for FloorSum<R, X, Y> where R: SemiRing {}
240
241fn floor_monoid_product<M>(
242    mut x: M::T,
243    mut y: M::T,
244    mut n: u64,
245    mut a: u64,
246    mut b: u64,
247    mut m: u64,
248) -> M::T
249where
250    M: Monoid,
251{
252    let mut c = (a * n + b) / m;
253    let mut pre = M::unit();
254    let mut suf = M::unit();
255    loop {
256        let (p, q) = (a / m, b / m);
257        a %= m;
258        b %= m;
259        x = M::operate(&x, &M::pow(y.clone(), p));
260        pre = M::operate(&pre, &M::pow(y.clone(), q));
261        c -= p * n + q;
262        if c == 0 {
263            break;
264        }
265        let d = (m * c - b - 1) / a + 1;
266        suf = M::operate(&y, &M::operate(&M::pow(x.clone(), n - d), &suf));
267        b = m - b - 1 + a;
268        n = c - 1;
269        c = d;
270        swap(&mut m, &mut a);
271        swap(&mut x, &mut y);
272    }
273    x = M::pow(x.clone(), n);
274    M::operate(&M::operate(&pre, &x), &suf)
275}
276
277/// $$\sum_{i=0}^{n-1}i^X\left\lfloor\frac{a\times i+b}{m}\right\rfloor^Y$$
278pub fn floor_sum_polynomial<T, const X: usize, const Y: usize>(
279    n: u64,
280    a: u64,
281    b: u64,
282    m: u64,
283) -> [[T; Y]; X]
284where
285    T: Clone + Zero + One + Add<Output = T> + Mul<Output = T>,
286{
287    debug_assert!(a == 0 || n < (u64::MAX - b) / a);
288    floor_monoid_product::<FloorSum<AddMulOperation<T>, X, Y>>(
289        FloorSum::<AddMulOperation<T>, X, Y>::to_x(),
290        FloorSum::<AddMulOperation<T>, X, Y>::to_y(),
291        n,
292        a,
293        b,
294        m,
295    )
296    .dp
297}
298
299/// $$\sum_{i=l}^{r-1}i^X\left\lfloor\frac{a\times i+b}{m}\right\rfloor^Y$$
300pub fn floor_sum_polynomial_i64<T, const X: usize, const Y: usize>(
301    l: i64,
302    r: i64,
303    a: i64,
304    b: i64,
305    m: u64,
306) -> [[T; Y]; X]
307where
308    T: Clone + Zero + One + Add<Output = T> + Mul<Output = T>,
309    AddMulOperation<T>: SemiRing<T = T, Additive: Invertible>,
310{
311    assert!(l <= r);
312    assert!(m > 0);
313
314    if a < 0 {
315        let mut ans = floor_sum_polynomial_i64::<T, X, Y>(-r + 1, -l + 1, -a, b, m);
316        for ans in ans.iter_mut().skip(1).step_by(2) {
317            for ans in ans.iter_mut() {
318                *ans = AddMulOperation::<T>::neg(ans);
319            }
320        }
321        return ans;
322    }
323
324    let add_x = l;
325    let n = (r - l) as u64;
326    let b = a * add_x + b;
327
328    let add_y = b.div_euclid(m as i64);
329    let b = b.rem_euclid(m as i64);
330    assert!(a >= 0);
331    assert!(b >= 0);
332    let data = floor_monoid_product::<FloorSum<AddMulOperation<T>, X, Y>>(
333        FloorSum::<AddMulOperation<T>, X, Y>::to_x(),
334        FloorSum::<AddMulOperation<T>, X, Y>::to_y(),
335        n,
336        a as u64,
337        b as u64,
338        m,
339    );
340
341    let offset = FloorSum::<AddMulOperation<T>, X, Y>::offset(add_x, add_y);
342    FloorSum::<AddMulOperation<T>, X, Y>::operate(&offset, &data).dp
343}
344
345#[derive(Debug)]
346struct FloorPowerSum<R>
347where
348    R: SemiRing,
349{
350    x: R::T,
351    sum: R::T,
352}
353
354impl<R> Clone for FloorPowerSum<R>
355where
356    R: SemiRing,
357{
358    fn clone(&self) -> Self {
359        Self {
360            x: self.x.clone(),
361            sum: self.sum.clone(),
362        }
363    }
364}
365
366impl<R> FloorPowerSum<R>
367where
368    R: SemiRing,
369{
370    fn to_x(x: R::T) -> Self {
371        Self { x, sum: R::one() }
372    }
373    fn to_y(y: R::T) -> Self {
374        Self {
375            x: y,
376            sum: R::zero(),
377        }
378    }
379}
380
381impl<R> Magma for FloorPowerSum<R>
382where
383    R: SemiRing,
384{
385    type T = Self;
386
387    fn operate(a: &Self::T, b: &Self::T) -> Self::T {
388        Self {
389            x: R::mul(&a.x, &b.x),
390            sum: R::add(&a.sum, &R::mul(&a.x, &b.sum)),
391        }
392    }
393}
394
395impl<R> Unital for FloorPowerSum<R>
396where
397    R: SemiRing,
398{
399    fn unit() -> Self::T {
400        Self {
401            x: R::one(),
402            sum: R::zero(),
403        }
404    }
405}
406
407impl<R> Associative for FloorPowerSum<R> where R: SemiRing {}
408
409/// $$\sum_{i=0}^{n-1}x^iy^{\left\lfloor\frac{a\times i+b}{m}\right\rfloor}$$
410pub fn floor_power_sum<R>(x: R::T, y: R::T, n: u64, a: u64, b: u64, m: u64) -> R::T
411where
412    R: SemiRing,
413{
414    floor_monoid_product::<FloorPowerSum<R>>(
415        FloorPowerSum::<R>::to_x(x),
416        FloorPowerSum::<R>::to_y(y),
417        n,
418        a,
419        b,
420        m,
421    )
422    .sum
423}
424
425#[cfg(test)]
426mod tests {
427    use super::*;
428    use crate::{num::mint_basic::MInt998244353, tools::Xorshift};
429
430    #[test]
431    fn test_floor_sum() {
432        const A: u64 = 1_000;
433        const B: i64 = 1_000;
434        const Q: usize = 1_000;
435        let mut rng = Xorshift::default();
436        for _ in 0..Q {
437            let (n, a, b, m) = rng.random((..A, ..A, ..A, 1..A));
438            let expected: u64 = (0..n).map(|i| (a * i + b) / m).sum();
439            let result = floor_sum(n, a, b, m);
440            assert_eq!(expected, result);
441
442            let (mut l, mut r, a, b) = rng.random((-B..B, -B..B, -B..B, -B..B));
443            if l > r {
444                swap(&mut l, &mut r);
445            }
446            let expected: i64 = (l..r).map(|i| (a * i + b).div_euclid(m as i64)).sum();
447            let result = floor_sum_i64(l, r, a, b, m);
448            assert_eq!(expected, result);
449
450            let (mut lv, mut rv) = rng.random((0..m as i64, 0..m as i64));
451            if lv > rv {
452                swap(&mut lv, &mut rv);
453            }
454            let range = lv..rv + 1;
455            let expected = (l..r)
456                .filter(|&i| range.contains(&(a * i + b).rem_euclid(m as i64)))
457                .count() as i64;
458            let result = floor_sum_range_freq(l, r, a, b, m, range);
459            assert_eq!(expected, result);
460        }
461    }
462
463    #[test]
464    fn test_floor_sum_polynomial() {
465        const P: usize = 3;
466        const A: u64 = 100;
467        const B: i64 = 100;
468        const Q: usize = 1_000;
469        let mut rng = Xorshift::default();
470        for _ in 0..Q {
471            let (n, a, b, m) = rng.random((..A, ..A, ..A, 1..A));
472            let mut expected: [[u64; P]; P] = [[0; P]; P];
473            for (x, expected) in expected.iter_mut().enumerate() {
474                for (y, expected) in expected.iter_mut().enumerate() {
475                    *expected = (0..n)
476                        .map(|i| i.pow(x as u32) * ((a * i + b) / m).pow(y as u32))
477                        .sum();
478                }
479            }
480            let result = floor_sum_polynomial::<u64, P, P>(n, a, b, m);
481            assert_eq!(expected, result);
482
483            let (mut l, mut r, a, b) = rng.random((-B..B, -B..B, -B..B, -B..B));
484            if l > r {
485                swap(&mut l, &mut r);
486            }
487            let mut expected: [[i64; P]; P] = [[0; P]; P];
488            for (x, expected) in expected.iter_mut().enumerate() {
489                for (y, expected) in expected.iter_mut().enumerate() {
490                    *expected = (l..r)
491                        .map(|i| i.pow(x as u32) * (a * i + b).div_euclid(m as i64).pow(y as u32))
492                        .sum();
493                }
494            }
495            let result = floor_sum_polynomial_i64::<i64, P, P>(l, r, a, b, m);
496            assert_eq!(expected, result);
497        }
498    }
499
500    #[test]
501    fn test_floor_power_sum() {
502        const A: u64 = 1_000;
503        const Q: usize = 1_000;
504        let mut rng = Xorshift::default();
505        for _ in 0..Q {
506            let (n, a, b, m) = rng.random((..A, ..A, ..A, 1..A));
507            let x: MInt998244353 = rng.random(..);
508            let y: MInt998244353 = rng.random(..);
509            let expected: MInt998244353 = (0..n)
510                .map(|i| {
511                    let floor = (a * i + b) / m;
512                    x.pow(i as _) * y.pow(floor as _)
513                })
514                .sum();
515            let result = floor_power_sum::<AddMulOperation<MInt998244353>>(x, y, n, a, b, m);
516            assert_eq!(expected, result);
517        }
518    }
519}