公式

G - Half-Baked 解説 by sheyasutaka 印刷ページ


定式化

\(A_1, \dots, A_N, B_1, \dots, B_N\) は相異なるとしても一般性を失いません(適当なタイブレークで順序を付ければ一般の場合でも成立する).

\(A_1, \dots, A_N, B_1, \dots, B_N\) を昇順に並べた列を \((C_1, \dots, C_{2N})\) とし,\(A_i = C_{a_i},\ B_i = C_{b_i}\) となるように \(a_i, b_i\) を定めます.

上から \(k\) 番目のパンケーキの大きさが \(A_i\) になる確率を \(g_A(i,k)\) としたとき,多項式 \(P_{a_i} := \sum_{k=1}^{N} g(i,k) x^{k-1}\) は \(j \in \{1, \dots, N\}\) に対する以下の値の総積として得られます.

  • \(j = i\) の場合,\(\frac{1}{2}\)
  • \(j \neq i\) かつ \(A_j, B_j < A_i\) の場合,\(x\)
  • \(j \neq i\) かつ \(A_j < A_i < B_j\) の場合,\(\frac{1}{2} + \frac{1}{2}x\)
  • \(j \neq i\) かつ \(A_i < A_j, B_j\) の場合,\(1\)

\(B_i\) についても同様のことが言えます.\(P_{b_i}\) を同様に定義します.

\(\sum_{k=1}^{N} f(k) x^{k-1} = \sum_{i = 1}^{2N} C_{i} P_{i}\) なので,これら \(2N\) 個の多項式を重ね合わせた総和を求めればよいです.

定式化 2

突然ですが,長さ \(2N\) の有理式列 \(L, M, R\) を,\(1 \leq i \leq N\) に対して以下のように定めます.

  • \(L_{a_i} = \frac{1}{2} + \frac{1}{2} x\)
  • \(L_{b_i} = x / (\frac{1}{2} + \frac{1}{2} x)\)
  • \(R_{a_i} = 1\)
  • \(R_{b_i} = 1\)
  • \(M_{a_i} = A_i\)
  • \(M_{b_i} = B_i / (\frac{1}{2} + \frac{1}{2} x)\)

このとき,

\[C_{i} P_{i} = \left( \prod_{l < i} L_l \cdot M_i \cdot \prod_{r > i} R_r \right)\]

なので,求める多項式は

\[\sum_{i = 1}^{2N} C_{i} P_{i} = \sum_{i=1}^{2N} \left( \prod_{l < i} L_l \cdot M_i \cdot \prod_{r > i} R_r \right)\]

となります.

高速な計算

区間 \([l, r)\) について,以下の \(3\) つの有理式の組 \((\mathrm{prodL}, \mathrm{prodR}, \mathrm{sumLMR})\) を対応する情報として考えます.

  • \(\mathrm{prodL} := \prod_{l \leq i < r} L_i\)
  • \(\mathrm{prodR} := \prod_{l \leq i < r} R_i\)
  • \(\mathrm{sumLMR} := \sum_{l \leq i < r} \left( \prod_{l \leq l' < i} L_{l'} \cdot M_i \cdot \prod_{i < r' < r} R_{r'} \right)\)

区間 \([l, m),[m, r)\) の情報から区間 \([l, r)\) の情報を復元することが可能です.

\(L, M, R\) の全要素を \((\frac{1}{2} + \frac{1}{2} x)\) 倍すると,結果はちょうど \((\frac{1}{2} + \frac{1}{2} x)^{2N}\) 倍になり,また各要素は高々 \(2\) 次の多項式になります.このとき,区間 \([l, m),[m, r)\) の情報から区間 \([l, r)\) の情報を復元するときの計算量は \(O((r-l) \log (r-l))\) (\(n\) 次の多項式どうしの積の計算は高速フーリエ変換を利用して \(O(N \log N)\) 時間で可能) となるため,分割統治法によって \(O(N \log^2 N)\) 時間で区間 \([0, N)\) の情報ひいては求める多項式を得ることができます.

実装例 (C++)

#pragma GCC optimize("unroll-loops")

#include <iostream>
using std::cin;
using std::cout;
using std::cerr;
using std::endl;
#include <vector>
using std::vector;
using std::pair;
#include <array>
using std::array;
#include <cassert>
using std::min;
using std::max;

#include <atcoder/modint.hpp>
using mint = atcoder::modint998244353;
#include <atcoder/convolution.hpp>
using atcoder::convolution;


#ifdef DEBUG
const int debug = 1;
#else
const int debug = 0;
#endif

using ll = int64_t;
using P = pair<ll, ll>;

const ll INF = 1'000'000'000'000'000'009LL;

ll n;
vector<ll> a, b;

inline void output (const ll x) {
	cout << x << "\n";
}

vector<mint> operator* (const mint &l, const vector<mint> &r) {
	vector<mint> x(r.size());
	for (ll i = 0; i < (ll)r.size(); i++) x[i] = l * r[i];
	return x;
}
vector<mint> operator* (const vector<mint> &l, const vector<mint> &r) {
	return convolution(l, r);
}
vector<mint> operator+ (const vector<mint> &l, const vector<mint> &r) {
	vector<mint> x(max(l.size(), r.size()), 0);
	for (ll i = 0; i < (ll)l.size(); i++) x[i] += l[i];
	for (ll i = 0; i < (ll)r.size(); i++) x[i] += r[i];
	return x;
}
vector<mint> operator- (const vector<mint> &l, const vector<mint> &r) {
	return l + (mint)(-1) * r;
}

vector<mint> fpsinv (const vector<mint> &x) {
	assert(x[0] != 0);
	vector<mint> y = {x[0].inv()};
	ll k = 1;

	while (k < (ll)x.size()) {
		vector<mint> xx(k*2);
		for (ll i = 0; i < k*2; i++) xx[i] = ((i < x.size()) ? x[i] : 0);

		y = 2 * y - xx * y * y;
		y.resize(k*2);
		k = k*2;
	}
	y.resize(x.size());
	return y;
}


void solve() {
	const mint inv2 = ((mint)2).inv();
	const vector<mint> inv2inv2 = (vector<mint>){inv2, inv2};

	vector<P> c(n*2);
	for (ll i = 0; i < n; i++) {
		c[i*2+0] = {a[i], i*2+0};
		c[i*2+1] = {b[i], i*2+1};
	}
	sort(c.begin(), c.end());

	vector<vector<mint>> cl(n*2), cm(n*2), cr(n*2);
	for (ll i = 0; i < n*2; i++) {
		ll v = c[i].second / 2;
		if (c[i].second % 2 == 0) {
			cl[i] = {inv2 * inv2, inv2, inv2 * inv2};
			cm[i] = {inv2 * a[v] * inv2, inv2 * a[v] * inv2};
			cr[i] = {inv2, inv2};
		} else {
			cl[i] = {0, 1};
			cm[i] = {inv2 * b[v]};
			cr[i] = {inv2, inv2};
		}
	}

	using Fpsdata = struct {
		vector<mint> pl;
		vector<mint> pr;
		vector<mint> sumlmr;
	};
	const auto findlmr = [&](this auto self, const ll l, const ll r) -> Fpsdata {
		if (r-l == 1) {
			Fpsdata ret = {
				pl: cl[l],
				pr: cr[l],
				sumlmr: cm[l]
			};
			return ret;
		}

		ll med = (l + r) / 2;
		Fpsdata fl = self(l, med);
		Fpsdata fr = self(med, r);

		Fpsdata ret = {
			pl: fl.pl * fr.pl,
			pr: fl.pr * fr.pr,
			sumlmr: fl.sumlmr * fr.pr + fl.pl * fr.sumlmr
		};
		return ret;
	};
	Fpsdata wholef = findlmr(0, n*2);

	vector<mint> inv2inv2pow2n(n*2+1);
	mint n2cr = 1;
	for (ll i = 0; i <= n*2; i++) {
		inv2inv2pow2n[i] = inv2.pow(i) * n2cr * inv2.pow(n*2-i);
		n2cr *= (n*2 - i);
		n2cr /= (i + 1);
	}
	// inv2inv2pow2n.resize(n+1);

	vector<mint> xl = wholef.sumlmr;
	xl.resize(n+1);
	vector<mint> xr = fpsinv(inv2inv2pow2n);
	xr.resize(n+1);
	vector<mint> ans = xl * xr;
	const mint pown = ((mint)2).pow(n);
	for (ll i = 0; i < n; i++) {
		cout << (ans[i] * pown).val() << "\n";
	}
}

int main (void) {
	std::cin.tie(nullptr);
	std::ios_base::sync_with_stdio(false);

	cin >> n;
	a.resize(n);
	b.resize(n);
	for (ll i = 0; i < n; i++) {
		cin >> a[i] >> b[i];
	}

	solve();

	
	return 0;
}

投稿日時:
最終更新: