1. 程式人生 > 實用技巧 >NFLSOJ410 【2019 江蘇省隊第一輪集訓】擔心

NFLSOJ410 【2019 江蘇省隊第一輪集訓】擔心

NFLSOJ410 【2019 江蘇省隊第一輪集訓】擔心

題目大意

題目連結

共有 \(n\) 個人參加比賽,這 \(n\) 個人站成一排。比賽採用單挑制,每次等概率選出兩個相鄰的人進行單挑,勝者保留,敗者淘汰。每個人的水平是不同的,第 \(i\) 個人的水平是 \(a_i\)。一場單挑如果在水平分別為 \(a\)\(b\) 的人之間進行,那麼前者獲勝的概率是 \(\frac{a}{a+b}\),後者獲勝的概率是 \(\frac{b}{a+b}\),不可能平局。這場比賽最終的勝者是最後剩下的唯一的人。

你有一個朋友。你已知這個朋友的位置 \(k\) (\(1\leq k\leq n\)),以及每個人的水平 \(a_{1\dots n}\)

。請求出這個朋友最終獲勝的概率。答案對 \(998244353\) 取模。

資料範圍:\(1\leq n\leq 500\)

本題題解

考慮樸素的區間 DP。設 \(\text{dp}(i,j,k)\) (\(1\leq i\leq k\leq j\leq n\)) 表示只考慮 \([i,j]\) 這段區間裡的人,在他們之間進行 \(j - i\) 次比賽後,最終留下來的人為 \(k\) 的概率 \(\times (j - i)!\)。也就是所有可能的操作順序下\(k\) 獲勝的概率之和。這樣定義是為了方便轉移(合併兩段區間)。

考慮轉移,即 \([i,j]\) 是怎麼來的。發現一定是區間“左半部分”決出了一個勝者 \(x\)

,“右半部分”決出了一個勝者 \(y\),然後 \(x,y\) 再比一場,決出最終的贏家,也就是 \(k\)。換句話說,一定存在一個分界線 \(l\) (\(i\leq l < j\)),使得 \(x,y\) 最終決鬥前, \(y\) 不會和 \([i,l]\) 裡的人有接觸,\(x\) 不會和 \([l + 1, j]\) 裡的人有接觸。這個性質是由“每次選擇相鄰的人進行決鬥”這一要求決定的。根據這個性質,在轉移時可以列舉 \(l\)。則:

\[\text{dp}(i,j,k) = \sum_{l = i}^{j - 1}\begin{cases} \sum_{y = l + 1}^{j}\text{dp}(i,l,k)\times\text{dp}(l + 1, j, y)\times \frac{a_k}{a_k + a_y}\times {j - i - 1\choose l - i} && k\leq l\\ \sum_{x = i}^{l}\text{dp}(i,l,x)\times\text{dp}(l + 1, j, k)\times \frac{a_k}{a_x + a_k}\times {j - i - 1\choose l - i} && k > l \end{cases} \]

上下兩行分別對應了 \(k\)\(x\)\(k\)\(y\) 的情況。

邊界是 \(\text{dp}(i,i,i) = 1\)。答案是 \(\text{dp}(1, n, k)\times \frac{1}{(n - 1)!}\)

這樣 DP 的時間複雜度是 \(\mathcal{O}(n^5\log n)\)\(\mathcal{O}(n^5)\),取決於是否預處理逆元。這個暴力 DP 的程式碼,我附在了參考程式碼部分。


考慮簡化狀態。

\(\text{dpl}(i,j) = \text{dp}(i,j,i)\), \(\text{dpr}(i, j) = \text{dp}(i,j,j)\),即只保留最終贏家是 \(i\)\(j\) 的狀態。

考慮轉移,以 \(\text{dpl}(i,j)\) 為例。列舉最後和 \(i\) 決鬥的點 \(k\) (\(i < k \leq j\))。一種想法是從 \(\text{dpl}(i,k - 1)\times\text{dpl}(k,j)\) 轉移過來。但這種想法是不對的。因為這代表我們默認了 \(k\) 就是左右兩部分的分界點,但這顯然不是全部的情況。因為可能存在有 \([i + 1, k - 1]\) 之間的人,也是被 \(k\) 打敗的,換句話說就是真實的分界點 \(l\) 小於 \(k\) 的情況,在這裡沒有被考慮到。

為了避免上述錯誤,我們再定義一個 \(\text{dplr}(i,j)\),表示只考慮 \([i,j]\) 區間裡的人,在進行 \(j - i - 1\) 次比賽後,剩下 \(i\)\(j\) 的概率 \(\times(j - i - 1)!\)。也就是目前 \(i\)\(j\) 都保留著,最終誰贏還尚未知曉。它顯然有轉移:

\[\text{dplr}(i,j) = \sum_{l = i}^{j - 1}\text{dpl}(i,l)\times \text{dpr}(l + 1, j)\times{j - i - 1\choose l - i} \]

然後我們再通過 \(\text{dplr}(i,k)\) 來轉移 \(\text{dpl}(i,j)\)

\[\text{dpl}(i,j) = \sum_{k = i + 1}^{j}\text{dplr}(i,k)\times\text{dpl}(k,j)\times\frac{a_i}{a_i + a_k}\times{j - i - 1\choose j - k} \]

類似地,也可以寫出 \(\text{dpr}(i,j)\) 的轉移:

\[\text{dpr}(i, j) = \sum_{k = i}^{j - 1}\text{dpr}(i,k)\times\text{dplr}(k, j)\times\frac{a_j}{a_k + a_j}\times{j - i - 1\choose k - i} \]

邊界是 \(\text{dpl}(i,i) = \text{dpr}(i, i) = 1\)。最終答案是 \(\text{dpr}(1,k)\times\text{dpl}(k, n)\times{n - 1\choose n - k}\times\frac{1}{(n - 1)!}\)

時間複雜度 \(\mathcal{O}(n^3)\)

參考程式碼

\(\mathcal{O}(n^3)\) AC 程式碼:

// problem: NFLSOJ410
#include <bits/stdc++.h>
using namespace std;

#define mk make_pair
#define fi first
#define se second
#define SZ(x) ((int)(x).size())

typedef unsigned int uint;
typedef long long ll;
typedef unsigned long long ull;
typedef pair<int, int> pii;

template<typename T> inline void ckmax(T& x, T y) { x = (y > x ? y : x); }
template<typename T> inline void ckmin(T& x, T y) { x = (y < x ? y : x); }

const int MAXN = 500;
const int MOD = 998244353;

inline void add(int& x, ll y) {
	x = ((ll)x + y) % MOD;
}
inline int pow_mod(int x, int i) {
	int y = 1;
	while (i) {
		if (i & 1) y = (ll)y * x % MOD;
		x = (ll)x * x % MOD;
		i >>= 1;
	}
	return y;
}

int fac[MAXN + 5], ifac[MAXN + 5];
inline int comb(int n, int k) {
	if (n < k) return 0;
	return (ll)fac[n] * ifac[k] % MOD * ifac[n - k] % MOD;
}
void facinit(int lim = MAXN) {
	fac[0] = 1;
	for (int i = 1; i <= lim; ++i) fac[i] = (ll)fac[i - 1] * i % MOD;
	ifac[lim] = pow_mod(fac[lim], MOD - 2);
	for (int i = lim - 1; i >= 0; --i) ifac[i] = (ll)ifac[i + 1] * (i + 1) % MOD;
}

int n, pos, a[MAXN + 5];
int pwin[MAXN + 5][MAXN + 5]; // pwin[i][j]: 在 (i, j) 比賽中, i 獲勝的概率
int dpl[MAXN + 5][MAXN + 5], dpr[MAXN + 5][MAXN + 5], dplr[MAXN + 5][MAXN + 5];

int main() {
	cin >> n >> pos;
	facinit(n);
	for (int i = 1; i <= n; ++i) {
		cin >> a[i];
	}
	for (int i = 1; i <= n; ++i) for (int j = 1; j <= n; ++j) {
		pwin[i][j] = (ll)a[i] * pow_mod((a[i] + a[j]) % MOD, MOD - 2) % MOD;
	}
	
	for (int i = 1; i <= n; ++i) {
		dpl[i][i] = dpr[i][i] = 1;
	}
	for (int len = 2; len <= n; ++len) {
		for (int i = 1; i + len - 1 <= n; ++i) {
			int j = i + len - 1;
			for (int k = i; k < j; ++k) {
				add(dplr[i][j], (ll)dpl[i][k] * dpr[k + 1][j] % MOD * comb(j - i - 1, k - i));
			}
			for (int k = i + 1; k <= j; ++k) {
				// add(dpl[i][j], (ll)dpl[i][k - 1] * dpl[k][j] % MOD * pwin[i][k] % MOD * comb(j - i - 1, j - k) % MOD);
				add(dpl[i][j], (ll)dplr[i][k] * dpl[k][j] % MOD * pwin[i][k] % MOD * comb(j - i - 1, j - k));
			}
			for (int k = i; k < j; ++k) {
				// add(dpr[i][j], (ll)dpr[i][k] * dpr[k + 1][j] % MOD * pwin[j][k] % MOD * comb(j - i - 1, k - i) % MOD);
				add(dpr[i][j], (ll)dpr[i][k] * dplr[k][j] % MOD * pwin[j][k] % MOD * comb(j - i - 1, k - i));
			}
		}
	}
	int ans = (ll)dpr[1][pos] * dpl[pos][n] % MOD * comb(n - 1, n - pos) % MOD * ifac[n - 1] % MOD;
	cout << ans << endl;
	return 0;
}

\(\mathcal{O}(n^5\log n)\)

// problem: NFLSOJ410
#include <bits/stdc++.h>
using namespace std;

#define mk make_pair
#define fi first
#define se second
#define SZ(x) ((int)(x).size())

typedef unsigned int uint;
typedef long long ll;
typedef unsigned long long ull;
typedef pair<int, int> pii;

template<typename T> inline void ckmax(T& x, T y) { x = (y > x ? y : x); }
template<typename T> inline void ckmin(T& x, T y) { x = (y < x ? y : x); }

const int MAXN = 50;
const int MOD = 998244353;

inline int mod1(int x) { return x < MOD ? x : x - MOD; }
inline int mod2(int x) { return x < 0 ? x + MOD : x; }
inline void add(int &x, int y) { x = mod1(x + y); }
inline void sub(int &x, int y) { x = mod2(x - y); }
inline int pow_mod(int x, int i) {
	int y = 1;
	while (i) {
		if (i & 1) y = (ll)y * x % MOD;
		x = (ll)x * x % MOD;
		i >>= 1;
	}
	return y;
}

int fac[MAXN + 5], ifac[MAXN + 5];
inline int comb(int n, int k) {
	if (n < k) return 0;
	return (ll)fac[n] * ifac[k] % MOD * ifac[n - k] % MOD;
}
void facinit(int lim = MAXN) {
	fac[0] = 1;
	for (int i = 1; i <= lim; ++i) fac[i] = (ll)fac[i - 1] * i % MOD;
	ifac[lim] = pow_mod(fac[lim], MOD - 2);
	for (int i = lim - 1; i >= 0; --i) ifac[i] = (ll)ifac[i + 1] * (i + 1) % MOD;
}

int n, p, a[MAXN + 5];
int dp[MAXN + 5][MAXN + 5][MAXN + 5];

int main() {
	cin >> n >> p;
	facinit(n);
	for (int i = 1; i <= n; ++i) {
		cin >> a[i];
	}
	for (int i = 1; i <= n; ++i) {
		dp[i][i][i] = 1;
	}
	for (int len = 2; len <= n; ++len) {
		for (int i = 1; i + len - 1 <= n; ++i) {
			int j = i + len - 1;
			for (int w = i; w <= j; ++w) { // winner
				for (int l = i; l < j; ++l) {
					if (w <= l) {
						for (int w2 = l + 1; w2 <= j; ++w2) {
							int pwin = (ll)a[w] * pow_mod((a[w] + a[w2]) % MOD, MOD - 2) % MOD;
							add(dp[i][j][w], (ll)dp[i][l][w] * dp[l + 1][j][w2] % MOD * comb(j - i - 1, l - i) % MOD * pwin % MOD);
						}
					} else {
						for (int w2 = i; w2 <= l; ++w2) {
							int pwin = (ll)a[w] * pow_mod((a[w] + a[w2]) % MOD, MOD - 2) % MOD;
							add(dp[i][j][w], (ll)dp[i][l][w2] * dp[l + 1][j][w] % MOD * comb(j - i - 1, l - i) % MOD * pwin % MOD);
						}
					}
				}
			}
		}
	}
	cout << ((ll)dp[1][n][p] * ifac[n - 1] % MOD) << endl;
	return 0;
}