Codeforces Round #524 (Div. 2) F. Katya and Segments Sets

定数倍高速化がだるかった。
Problem - F - Codeforces

問題

k個の区間[l_i, r_i] (1 <= i <= k)がある。各区間には色c_iがついている。色の種類はn種類である。このとき、以下のm個のクエリにオンラインで答えよ。

  • クエリ(a, b, x, y): a色からb色までの色すべてについて、区間[x, y]に完全に含まれる区間が存在するか判定せよ。全ての色について存在するなら"yes"を、どれか1色でも存在しなければ"no"を出力せよ。

解法

遅延セグメント木を実装せよという問題なので、せっかくなのでRustで実装した。

実装上の注意点

  • 子供をVecで管理するのは微妙に遅いので、定数倍高速化のためにOption>を使った方が良い。
  • 入力を受け取るときにstdinをlockしないと遅い。

提出: Submission #47791444 - Codeforces (Rust)

// Verified by https://codeforces.com/contest/1080/submission/47790998
trait Monoid: Clone {
    type T: Clone;
    fn e() -> Self::T;
    fn op(&Self::T, &Self::T) -> Self::T;
}

#[derive(Clone, Debug)]
struct PersistentSegTree<M: Monoid> {
    size: usize,
    val: M::T,
    left: Option<std::rc::Rc<PersistentSegTree<M>>>,
    right: Option<std::rc::Rc<PersistentSegTree<M>>>,
}

impl<M: Monoid> PersistentSegTree<M> {
    pub fn new(n: usize) -> Self {
        use std::rc::Rc;
        assert!(n >= 1);
        if n == 1 {
            PersistentSegTree {
                size: 1,
                val: M::e(),
                left: None,
                right: None,
            }
        } else {
            let l = Self::new(n / 2);
            let r = Self::new(n - n / 2);
            PersistentSegTree {
                size: n,
                val: M::e(),
                left: Some(Rc::new(l)),
                right: Some(Rc::new(r)),
            }
        }
    }
    // This function breaks the immutability of self.
    pub fn inplace_update(&mut self, v: &M::T) {
        self.val = v.clone();
        if let Some(ref mut left) = self.left {
            std::rc::Rc::make_mut(left).inplace_update(v);
        }
        if let Some(ref mut right) = self.right {
            std::rc::Rc::make_mut(right).inplace_update(v);
        }
    }
    pub fn update(&self, k: usize, val: M::T) -> Self {
        use std::rc::Rc;
        let size = self.size;
        assert!(k < size);
        if size == 1 {
            return PersistentSegTree {
                size: 1,
                val: val,
                left: None,
                right: None,
            };
        }
        let mut l = Rc::clone(self.left.as_ref().unwrap());
        let mut r = Rc::clone(self.right.as_ref().unwrap());
        if k < size / 2 {
            l = Rc::new(l.update(k, val));
        } else {
            r = Rc::new(r.update(k - size / 2, val));
        }
        PersistentSegTree {
            size: self.size,
            val: M::op(&l.val, &r.val),
            left: Some(l),
            right: Some(r),
        }
    }
    pub fn query(&self, a: usize, b: usize) -> M::T {
        let size = self.size;
        assert!(b <= size);
        if a >= b { return M::e(); }
        if a == 0 && b == size { return self.val.clone(); }
        debug_assert!(size >= 2);
        let (lo, hi) = (a, min(b, size / 2));
        let left = self.left.as_ref().unwrap().query(lo, hi);
        let (lo, hi) = (max(a, size / 2) - size / 2, max(b, size / 2) - size / 2);
        let right = self.right.as_ref().unwrap().query(lo, hi);
        M::op(&left, &right)
    }
}

const INF: i64 = 1 << 50;

#[derive(Clone)]
struct PMin;
impl Monoid for PMin {
    type T = i64;
    fn e() -> i64 { INF }
    fn op(&a: &i64, &b: &i64) -> i64 { min(a, b) }
}

fn solve() {
    let n: usize = get();
    let m: usize = get();
    let k: usize = get();
    let mut events = Vec::new();
    for _ in 0 .. k {
        let l: i64 = get();
        let r: i64 = get();
        let p: usize = get::<usize>() - 1;
        events.push((r, l, p));
    }
    events.sort_unstable();
    let mut chrono = Vec::new();
    chrono.push(PersistentSegTree::<PMin>::new(n));
    chrono[0].inplace_update(&(-1 << 20));
    for pos in 0 .. events.len() {
        let (_, l, p) = events[pos];
        let old = chrono[pos].query(p, p + 1);
        let next = chrono[pos].update(p, max(old, l));
        chrono.push(next);
    }
    for _ in 0 .. m {
        let a: usize = get::<usize>() - 1;
        let b: usize = get::<usize>();
        let x: i64 = get();
        let y: i64 = get();
        let idx = events.binary_search(&(y, i64::max_value(), 0)).unwrap_err();
        let res = chrono[idx].query(a, b);
        if res >= x {
            println!("yes");
        } else {
            println!("no");
        }
    }
}

まとめ

高速化を手伝ってくれた某氏には感謝しかない。

AGC030 D - Inversion Sum

「理由: AGCなので」という考察を初めてやった。
D - Inversion Sum

問題

長さNの整数列Aに対して、以下の操作をi = 1, ..., Qとして順にQ回行う。

  • A[X_i]とA[Y_i]を入れかえる。あるいは何もしない。

最終的に2^Q通りの操作のしかたがあるが、それぞれについて、操作後のAの転倒数の総和をmod 10^9+7で求めよ。

解法

この手の(各操作するかしないかのn択があるときの)数え上げの計算は期待値の計算と同値。和の期待値は期待値の和なので、結局A[i] < A[j]なる(i,j)について、各操作を確率1/2で行うときに最終的に転倒する確率をmod 10^9+7で求めれば良い。
(i, j)について、A[i]とA[j]を確率1/2で交換すると、以後どのような操作を行っても転倒する確率は1/2である。このことから、P[i][j]をもともとiにあったものがjに行く確率とすると、P[i][k] * P[i][k] / 2とP[i][k] * P[j][l] (j > l)の和を求めればいいような気がしてくるが、実際そうである。(理由: AGCなので)
これを実験するとうまく行く。愚直にやるとPの計算にO(NQ)、和の計算にO(N^4)かかるが、和の計算パートは累積和をうまく管理するとO(N^2)にできる。
計算量はO(NQ + N^2)。

実装上の注意点

  • 累積和の計算をバグらせないようにする。
  • 添字がたくさん登場するので、間違った添字を使わないようにする。

提出: Submission #3895399 - AtCoder Grand Contest 030 (C++)

// ModInt 省略

const int N = 3010;
ModInt<> dp[N][N];
ModInt<> acc[N][N];

int main(void) {
  ios::sync_with_stdio(false);
  cin.tie(0);
  int n, q;
  cin >> n >> q;
  VL a(n);
  REP(i, 0, n) cin >> a[i];
  VI x(q), y(q);
  REP(i, 0, q) {
    cin >> x[i] >> y[i];
    x[i]--, y[i]--;
  }
  REP(i, 0, n) {
    dp[i][i] = 1;
  }
  ModInt<> two(2);
  ModInt<> twoinv = two.inv();
  REP(i, 0, q) {
    REP(j, 0, n) {
      ModInt<> tmp = (dp[j][x[i]] + dp[j][y[i]]) * twoinv;
      dp[j][x[i]] = tmp;
      dp[j][y[i]] = tmp;
    }
  }
  REP(i, 0, n) {
    REP(j, 0, n) acc[i][j + 1] = acc[i][j] + dp[i][j];
  }
  vector<PI> pool;
  REP(i, 0, n) pool.push_back(PI(a[i], i));
  sort(pool.rbegin(), pool.rend());
  vector<ModInt<> > tap(n + 1);
  VI que;
  // TODO proof
  ModInt<> tot = 0;
  REP(i, 0, n) {
    if (DEBUG) cerr << "pool:" << pool[i].first << " " << pool[i].second << endl;
    if (i > 0 && pool[i].first != pool[i - 1].first) {
      // tap
      for (int t: que) {
        if (DEBUG) cerr << "pushing " << t << endl;
        REP(j, 0, n + 1) {
          tap[j] += acc[t][j];
        }
      }
      que.clear();
    }
    int idx = pool[i].second;
    REP(k, 0, n) {
      tot += dp[idx][k] * tap[k];
      tot += dp[idx][k] * (tap[k + 1] - tap[k]) * twoinv;
    }
    que.push_back(idx);
  }
  REP(i, 0, q) tot *= two;
  cout << tot << endl;
}

まとめ

本番はsolved数がC < Dだったので、まずDから解いた。
手計算で仮説を確かめようとしたら計算ミスをして仮説が間違っていると勘違いしてしまい、10分くらい無駄にした。

Educational Codeforces Round 57 (Rated for Div. 2) G. Lucky Tickets

NTTのお手本みたいな問題だった。
Problem - G - Codeforces

問題

nを偶数とする。長さnの数字列は、先頭n/2個の和と末尾n/2個の和が等しい時luckyであると呼ばれる。
数字列に出現できる数の集合{d_1, ..., d_k}が決まっている時、luckyな数字列の総数をmod 998244353で求めよ。

  • 2 <= n <= 2 * 10^5
  • k <= 10
  • d_kは相異なる0から9までの数字

解法

長さn/2の数字列の和としてあり得るものと、その場合の数を求めれば良い。
DP[今の位置][今の総和]というDPを考えると、自然に数え上げナップサックの形になる。
数え上げナップサックは自然に多項式の積に帰着できることが多い。今回も帰着でき、(x^{d_1} + ... + x^{d_k})^{n/2}の係数を計算すれば良いことになる。
これは順NTT -> 各値をn/2乗 -> 逆NTTで計算できる。

実装上の注意点

  • NTTを使う時はmod がNTT-friendlyか確認する。今回は998244353 = 119 * 2^23 + 1なので、2^23要素までのNTTが可能である。
  • 扱う多項式の次数の最大値を正しく見積もる。今回は最大9次の多項式を最大100000乗するので、2^20-1次まで管理できれば十分である。よって、2^20要素のNTTで十分である。

提出: Submission #47673213 - Codeforces (Rust)

// ModInt省略

mod fft {
    use std::ops::*;
    /// n should be a power of 2. zeta is a primitive n-th root of unity.
    /// one is unity
    /// Note that the result should be multiplied by 1/sqrt(n).
    pub fn transform<R>(f: &mut [R], zeta: R, one: R)
        where R: Copy +
        Add<Output = R> +
        Sub<Output = R> +
        Mul<Output = R> {
        let n = f.len();
        assert!(n.is_power_of_two());
        {
            let mut i = 0;
            for j in 1 .. n - 1 {
                let mut k = n >> 1;
                loop {
                    i ^= k;
                    if k <= i { break; }
                    k >>= 1;
                }
                if j < i { f.swap(i, j); }
            }
        }
        let mut zetapow = Vec::new();
        {
            let mut m = 1;
            let mut cur = zeta;
            while m < n {
                zetapow.push(cur);
                cur = cur * cur;
                m *= 2;
            }
        }
        let mut m = 1;
        while m < n {
            let base = zetapow.pop().unwrap();
            let mut r = 0;
            while r < n {
                let mut w = one;
                for s in r .. r + m {
                    let u = f[s];
                    let d = f[s + m] * w;
                    f[s] = u + d;
                    f[s + m] = u - d;
                    w = w * base;
                }
                r += 2 * m;
            }
            m *= 2;
        }
    }
}

fn solve() {
    let out = std::io::stdout();
    let mut out = BufWriter::new(out.lock());
    macro_rules! puts {
        ($($format:tt)*) => (write!(out,$($format)*).unwrap());
    }
    input! {
        n: usize,
        k: usize,
        d: [usize; k],
    }
    const N: usize = 1 << 20;
    let one = ModInt::new(1);
    let mut f = vec![ModInt::new(0); N];
    let zeta = ModInt::new(3).pow((MOD - 1) / N as i64);
    for i in 0 .. k {
        f[d[i]] += one;
    }
    fft::transform(&mut f, zeta, one);
    for i in 0 .. N {
        f[i] = f[i].pow(n as i64 / 2);
    }
    fft::transform(&mut f, zeta.inv(), one);
    let factor = ModInt::new(N as i64).inv();
    for i in 0 .. N {
        f[i] *= factor;
    }
    let mut tot = ModInt::new(0);
    for i in 0 .. N {
        tot += f[i].pow(2);
    }
    puts!("{}\n", tot);
}

まとめ

世界がすべて多項式に見えるタイプではない人はこういう問題をどうやって解くんだろう…?

Codeforces Round #529 (Div. 3) F. Make It Connected

まあ、基本ですね。
Problem - F - Codeforces

問題

長さnの数列aが与えられる。このaを用いて、n頂点の完全グラフであって、iとjを結ぶ辺のコストがa[i]+a[j]であるものを作る。
この完全グラフにm本の辺(x_i,y_i,w_i)を加える(w_iはコスト)。加えた後のグラフの最小全域木のコストを求めよ。
1 <= n <= 2 * 10^5
0 <= m <= 2 * 10^5
1 <= a_i <= 10^12
1 <= w_i <= 10^12

解法

最小全域木はKruskal法、Prim法(、時々Boruvka法)を適用すべしの原則に基づいて何を適用するかを考えると、今回はPrim法がぴったりであることがわかる。
a[v]が最小となるvを選び、vからなる1点集合{v}にa[i]+a[v]や加えられた辺によるコストの小さい順に頂点を追加していけばよい。

実装上の注意点

  • 単に頂点0に点を追加していく方針でもおそらくできるが、a[v]が最小の点vから始める方が楽。
  • コストが64bit整数の範囲内に収まるか確認する。今回は最大10^12 * 2*10^5 = 2*10^17 < 2^60なのでOK。

提出: https://codeforces.com/contest/1095/submission/47608560 (Rust)

fn solve() {
    let out = std::io::stdout();
    let mut out = BufWriter::new(out.lock());
    macro_rules! puts {
        ($($format:tt)*) => (write!(out,$($format)*).unwrap());
    }
    input! {
        n: usize,
        m: usize,
        a: [i64; n],
        xyw: [(usize1, usize1, i64); m],
    }
    let mut g = vec![Vec::new(); n];
    for (x, y, w) in xyw {
        g[x].push((y, w));
        g[y].push((x, w));
    }
    let mut amin = (1 << 50, n);
    for i in 0 .. n { amin = min(amin, (a[i], i)); }
    // Prim
    let mut tot = 0;
    let (added, init) = amin;
    let mut used = vec![false; n];
    used[init] = true;
    let mut que = BinaryHeap::new();
    for i in 0 .. n {
        if init != i {
            que.push(Reverse((a[i] + added, i)));
        }
    }
    for &(w, cost) in g[init].iter() {
        que.push(Reverse((cost, w)));
    }
    while let Some(Reverse((cost, v))) = que.pop() {
        if used[v] { continue; }
        tot += cost;
        used[v] = true;
        for &(w, cost) in g[v].iter() {
            que.push(Reverse((cost, w)));
        }
    }
    puts!("{}\n", tot)
}

まとめ

Prim法書いたことなかったので練習になってよかった。

第5回 ドワンゴからの挑戦状 本選 B - XOR Spread

これを解けそうな人がCやDに突っ込んだからか、あまり解かれなかった。
B - XOR Spread

問題

長さNの整数列aが与えられる。以下の操作を何回でも繰り返せる:

  • 1 < i < Nなるiを選び、a[i-1] ^= a[i]; a[i+1] ^= a[i];を実行する。 (^= はXORを行って更新する処理を表す)

このとき、最終的に得られるaとして辞書順最小のものを求めよ。

  • 1 <= N <= 10^5
  • 0 <= a_i <= 10^9

解法

おもむろにb[i] = xor_{j <= i} a[j]という長さN+1の配列{b[0], b[1], ..., b[N]}を考えると、実は問題の操作はswap(b[i], b[i-1])と等価であることがわかる。つまり、b[1], ... ,b[N - 1]は自由に並べ替えることができる。
今回欲しいのは、対応するaが辞書順で最小の配列になるようなbである。そのため、まずa[1] = b[1]を最小化して、次にa[2] = b[2] xor b[1]が最小になるように残りの中からb[2]を選び、次にa[3] = b[3] xor b[2]が最小になるように残りの中から…ということを繰り返せばよい。
数の多重集合の中から与えられた数とのxorが最小になるものを選ぶのは、愚直にやるとO(多重集合のサイズ)時間かかるが、binary trieというデータ構造を使うことで1個あたりO(ビット数)時間でできる。また多重集合から要素を削除する必要もあるが、binary trieを使えば同様にO(ビット数)時間である。

実装上の注意点

  • a <= 10^9だから、trieにはデータを(少なくとも)31bit分保持する必要がある。
  • (Rust限定) どのデータの所有権を誰が持つかを考えてデータ構造を設計する必要がある。今回の場合木構造なので、親が子孫全ての所有権を持つと思うと自然に実装できる。

提出: Submission #3883421 - 第5回 ドワンゴからの挑戦状 本選 (Rust)

#[derive(Clone)]
struct Trie {
    ch: Vec<Option<Box<Trie>>>,
    count: i32,
    level: usize,
}

impl Trie {
    fn new(level: usize) -> Trie {
        Trie {
            ch: vec![None; 2],
            count: 0,
            level: level,
        }
    }
    fn add(&mut self, a: i32) {
        self.count += 1;
        let level = self.level;
        if level == 0 {
            return;
        }
        let bit = (a >> (level - 1) & 1) as usize;
        if self.ch[bit].is_none() {
            self.ch[bit] = Some(Box::new(Trie::new(level - 1)));
        }
        self.ch[bit].as_mut().unwrap().add(a);
    }
    fn min(&self, a: i32, acc: i32) -> i32 {
        assert!(self.count > 0);
        let level = self.level;
        if level == 0 {
            return acc;
        }
        let mut bit = (a >> (level - 1) & 1) as usize;
        let first_count = match self.ch[bit] {
            None => 0,
            Some(ref ch) => ch.count,
        };
        if first_count == 0 {
            bit = 1 - bit;
        }
        self.ch[bit].as_ref().unwrap().min(a, acc ^ (bit as i32) << (level - 1))
    }
    fn remove(&mut self, a: i32) {
        self.count -= 1;
        let level = self.level;
        if level == 0 {
            return;
        }
        let bit = (a >> (level - 1) & 1) as usize;
        assert!(self.ch[bit].is_some());
        self.ch[bit].as_mut().unwrap().remove(a);
    }
}

fn solve() {
    let out = std::io::stdout();
    let mut out = BufWriter::new(out.lock());
    macro_rules! puts {
        ($($format:tt)*) => (write!(out,$($format)*).unwrap());
    }
    input! {
        n: usize,
        a: [i32; n],
    }
    let mut b = vec![0; n + 1];
    for i in 0 .. n {
        b[i + 1] = b[i] ^ a[i];
    }
    let mut trie = Trie::new(31);
    for i in 1 .. n {
        trie.add(b[i]);
    }
    let mut ans = vec![0; n + 1];
    for i in 1 .. n {
        let next = trie.min(ans[i - 1], 0);
        ans[i] = next;
        trie.remove(next);
    }
    ans[n] = b[n];
    for i in (0 .. n).rev() {
        ans[i + 1] ^= ans[i];
    }
    for i in 0 .. n {
        puts!("{}{}", ans[i + 1], if i == n - 1 { "\n" } else { " " });
    }
}

まとめ

テスターをやった時は解法を思いつくまでに45分かかりました。

パ研合宿コンペティション 3日目 F - ミックスジュース

問題文は閲覧注意です。
F - ミックスジュース

問題

「A から B までの整数から 1 個以上を選んだ時の和として得られる整数は何通りあるかmod 10^9+7で答えよ」という問いに Q 回答えよ。
1 <= A <= B <= 10^9
Q <= 10^5

解法

AからBまでの整数のうちi個使う場合、和はi(2A+i-1)/2からi(2B-i+1)/2までの整数のどれかである。
これから、i=1,...,B-A+1について閉区間[i(2A+i-1)/2,i(2B-i+1)/2]の和集合をとり、そこに含まれる整数の個数を数えれば良い。
当然左端も右端もiにしたがって単調増加するので、求める値は(B-A+1番目の右端) - (1番目の左端) + 1 - \sum_{i = 1}^{B - A} max(0, (i + 1番目の左端) - (i番目の右端) - 1)である。
第1項と第2項は簡単。第3項については、(i + 1番目の左端) - (i番目の右端) - 1がiの2次式であり、この2次式のi=0とi=B-Aでの値がA-1であることがわかるので、これが0以上である範囲を求めればよい。そのような範囲はi=(B-A)/2を軸として点対称である。

実装上の注意点

  • 端の扱いに気をつける
  • 2次方程式の解は二分探索で求める

提出: Submission #3878249 - パ研合宿コンペティション 3日目 (Rust)

// ModInt 省略
fn calc(a: i64, b: i64) -> ModInt {
    let one = ModInt::new(1);
    let two = ModInt::new(2);
    let am = ModInt::new(a);
    let bm = ModInt::new(b);
    let mut tot = (bm + am + one) * (bm - am) * two.inv() + one;
    let half = (b - a) / 2;
    let mut pass = 0;
    let mut fail = half + 1;
    while fail - pass > 1 {
        let mid = (fail + pass) / 2;
        if mid * (a - b + mid) + a - 1 >= 0 {
            pass = mid;
        } else {
            fail = mid;
        }
    }
    if pass == half {
        // i^2 + (a - b)i + a - 1 (i = 1 .. b - a)
        let bma = bm - am;
        tot -= bma * (bma + one) * (two * bma + one) * ModInt::new(6).inv();
        tot += bma * bma * (bma + one) * two.inv();
        tot -= (am - one) * bma;
    } else {
        // 2 * (i^2 + (a - b)i + a - 1) (i = 1 .. pass) + (a - 1)
        let x = ModInt::new(pass);
        tot -= x * (x + one) * (two * x + one) * ModInt::new(3).inv();
        tot -= (am - bm) * x * (x + one);
        tot -= two * (am - one) * x;
        tot -= am - one;
    }
    tot
}

fn solve() {
    let out = std::io::stdout();
    let mut out = BufWriter::new(out.lock());
    macro_rules! puts {
        ($format:expr) => (write!(out,$format).unwrap());
        ($format:expr, $($args:expr),+) => (write!(out,$format,$($args),*).unwrap())
    }
    input! {
        ab: [(i64, i64)],
    }
    for (a, b) in ab {
        puts!("{}\n", calc(a, b));
    }
}

まとめ

この問題文で中高生が参加するコンテストに出題するのはまずくないですか…?

第5回 ドワンゴからの挑戦状 本選 D - Parentheses Inversions

もともと分割統治NTTでO(N log^2 N)想定だったのを高速化して出題した。
D - Parentheses Inversions

問題

長さNの括弧列Sがある。1からNまでの整数の上の置換pであって、Sをpで置換したものがバランスの取れた括弧列であるときのpの転倒数を合計し、mod 10^9+7で答えよ。

  • 2 <= N <= 500000
  • Sに含まれる'('と')'の個数は等しい。

解法

(省略)

実装上の注意点

  • とにかく手数が多い。係数として掛けるべき数も複雑なので、愚直解と比較すべきである。

提出: Submission #3868089 - 第5回 ドワンゴからの挑戦状 本選 (rust)

fn solve() {
    let out = std::io::stdout();
    let mut out = BufWriter::new(out.lock());
    macro_rules! puts {
        ($format:expr) => (write!(out,$format).unwrap());
        ($format:expr, $($args:expr),+) => (write!(out,$format,$($args),*).unwrap())
    }
    input! {
        n: usize,
        s: chars,
    }
    let m = n / 2;
    let mut fac = vec![ModInt::new(1); n + 1];
    let mut invfac = vec![ModInt::new(1); n + 1];
    for i in 1 .. n + 1 {
        fac[i] = fac[i - 1] * ModInt::new(i as i64);
    }
    invfac[n] = fac[n].inv();
    for i in (0 .. n).rev() {
        invfac[i] = invfac[i + 1] * ModInt::new(i as i64 + 1);
    }
    let mut catalan = vec![ModInt::new(0); m + 1];
    for i in 0 .. m + 1 {
        catalan[i] = fac[2 * i] * invfac[i] * invfac[i + 1];
    }
    let mut sinv: i64 = 0;
    let mut close = 0;
    for i in 0 .. n {
        if s[i] == '(' {
            sinv += close;
        } else {
            close += 1;
        }
    }
    let mut tot = ModInt::new(0);
    tot += catalan[m] * fac[m].pow(2) * ModInt::new(m as i64 * (m - 1) as i64)
        * ModInt::new(2).inv(); // '('-'(', ')'-')'
    let mut d = vec![ModInt::new(0); m + 1]; // ')' - '('
    let mut e = vec![ModInt::new(0); m + 1]; // '(' - ')'
    // t^2 * P' / (1-4t)
    for i in 2 .. m + 1 {
        d[i] = ModInt::new(i as i64 - 1) * catalan[i - 1];
    }
    for i in 0 .. m {
        d[i + 1] += d[i] * ModInt::new(4);
    }
    for i in 0 .. m + 1 {
        e[i] = catalan[i] * ModInt::new(i as i64).pow(2) - d[i];
    }
    let opposite = ModInt::new(m as i64 * m as i64 - sinv);
    let sinv = ModInt::new(sinv);
    tot += (e[m] * sinv + d[m] * opposite) * fac[m - 1].pow(2);
    puts!("{}\n", tot);
}

まとめ

かなり難しくしてしまったが、最終防衛問題としての役割を果たしてくれてよかった。