zoukankan      html  css  js  c++  java
  • NFLSOJ410 【2019 江苏省队第一轮集训】担心

    NFLSOJ410 【2019 江苏省队第一轮集训】担心

    题目大意

    题目链接

    共有 (n) 个人参加比赛,这 (n) 个人站成一排。比赛采用单挑制,每次等概率选出两个相邻的人进行单挑,胜者保留,败者淘汰。每个人的水平是不同的,第 (i) 个人的水平是 (a_i)。一场单挑如果在水平分别为 (a)(b) 的人之间进行,那么前者获胜的概率是 (frac{a}{a+b}),后者获胜的概率是 (frac{b}{a+b}),不可能平局。这场比赛最终的胜者是最后剩下的唯一的人。

    你有一个朋友。你已知这个朋友的位置 (k) ((1leq kleq n)),以及每个人的水平 (a_{1dots n})。请求出这个朋友最终获胜的概率。答案对 (998244353) 取模。

    数据范围:(1leq nleq 500)

    本题题解

    考虑朴素的区间 DP。设 ( ext{dp}(i,j,k)) ((1leq ileq kleq jleq n)) 表示只考虑 ([i,j]) 这段区间里的人,在他们之间进行 (j - i) 次比赛后,最终留下来的人为 (k) 的概率 ( imes (j - i)!)。也就是所有可能的操作顺序下(k) 获胜的概率之和。这样定义是为了方便转移(合并两段区间)。

    考虑转移,即 ([i,j]) 是怎么来的。发现一定是区间“左半部分”决出了一个胜者 (x),“右半部分”决出了一个胜者 (y),然后 (x,y) 再比一场,决出最终的赢家,也就是 (k)。换句话说,一定存在一个分界线 (l) ((ileq l < j)),使得 (x,y) 最终决斗前, (y) 不会和 ([i,l]) 里的人有接触,(x) 不会和 ([l + 1, j]) 里的人有接触。这个性质是由“每次选择相邻的人进行决斗”这一要求决定的。根据这个性质,在转移时可以枚举 (l)。则:

    [ ext{dp}(i,j,k) = sum_{l = i}^{j - 1}egin{cases} sum_{y = l + 1}^{j} ext{dp}(i,l,k) imes ext{dp}(l + 1, j, y) imes frac{a_k}{a_k + a_y} imes {j - i - 1choose l - i} && kleq l\ sum_{x = i}^{l} ext{dp}(i,l,x) imes ext{dp}(l + 1, j, k) imes frac{a_k}{a_x + a_k} imes {j - i - 1choose l - i} && k > l end{cases} ]

    上下两行分别对应了 (k)(x)(k)(y) 的情况。

    边界是 ( ext{dp}(i,i,i) = 1)。答案是 ( ext{dp}(1, n, k) imes frac{1}{(n - 1)!})

    这样 DP 的时间复杂度是 (mathcal{O}(n^5log n))(mathcal{O}(n^5)),取决于是否预处理逆元。这个暴力 DP 的代码,我附在了参考代码部分。


    考虑简化状态。

    ( ext{dpl}(i,j) = ext{dp}(i,j,i)), ( ext{dpr}(i, j) = ext{dp}(i,j,j)),即只保留最终赢家是 (i)(j) 的状态。

    考虑转移,以 ( ext{dpl}(i,j)) 为例。枚举最后和 (i) 决斗的点 (k) ((i < k leq j))。一种想法是从 ( ext{dpl}(i,k - 1) imes ext{dpl}(k,j)) 转移过来。但这种想法是不对的。因为这代表我们默认了 (k) 就是左右两部分的分界点,但这显然不是全部的情况。因为可能存在有 ([i + 1, k - 1]) 之间的人,也是被 (k) 打败的,换句话说就是真实的分界点 (l) 小于 (k) 的情况,在这里没有被考虑到。

    为了避免上述错误,我们再定义一个 ( ext{dplr}(i,j)),表示只考虑 ([i,j]) 区间里的人,在进行 (j - i - 1) 次比赛后,剩下 (i)(j) 的概率 ( imes(j - i - 1)!)。也就是目前 (i)(j) 都保留着,最终谁赢还尚未知晓。它显然有转移:

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

    然后我们再通过 ( ext{dplr}(i,k)) 来转移 ( ext{dpl}(i,j))

    [ ext{dpl}(i,j) = sum_{k = i + 1}^{j} ext{dplr}(i,k) imes ext{dpl}(k,j) imesfrac{a_i}{a_i + a_k} imes{j - i - 1choose j - k} ]

    类似地,也可以写出 ( ext{dpr}(i,j)) 的转移:

    [ ext{dpr}(i, j) = sum_{k = i}^{j - 1} ext{dpr}(i,k) imes ext{dplr}(k, j) imesfrac{a_j}{a_k + a_j} imes{j - i - 1choose k - i} ]

    边界是 ( ext{dpl}(i,i) = ext{dpr}(i, i) = 1)。最终答案是 ( ext{dpr}(1,k) imes ext{dpl}(k, n) imes{n - 1choose n - k} imesfrac{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^5log 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;
    }
    
  • 相关阅读:
    进程状态
    VMware虚拟机的三种联网方法及原理
    关于C++迭代器失效
    头文件:limits.h、float.h
    正则表达式之一:元符号
    MYSQL之批量插入数据库
    PHP之如何判断数字(数字字符串不算)
    使用Process Monitor来得到程序运行参数
    Abusing the C preprocessor
    1+1还是1+1=2?
  • 原文地址:https://www.cnblogs.com/dysyn1314/p/14255012.html
Copyright © 2011-2022 走看看