【CF1156F】Sports Betting

题目

题目链接:https://codeforces.com/problemset/problem/1556/F
\(n\) 个人,两两之间会打比赛。每人有一个实力值 \(a_i\),在 \(i\)\(j\) 的比赛中, \(i\)\(\frac {a_i}{a_i+a_j}\) 的概率获胜,其他情况则是 \(j\) 获胜。\(i\) 在与 \(j\) 的比赛中获胜则称 \(i\) 打败了 \(j\)。若 \(i\) 打败了 \(j\)\(j\) 打败了 \(k\),则认为 \(i\) 也打败了 \(k\)。若 \(i\) 打败了除了他自己以外的所有人,则称 \(i\) 是一个 Winner(是否打败了自己不要求),注意 Winner 可能有多个。现在你需要求出 Winner 的期望数量,对 \(10^9+7\) 取模。
\(n\leq 14\)\(1\leq a_i\leq 10^6\)

思路

也就是一张竞赛图,每一条边有 \(\frac{a_i}{a_i+a_j}\) 的概率连向 \(i\)\(\frac{a_j}{a_i+a_j}\) 的概率连向 \(j\),求期望多少个点可以到达所有点。
\(f[i][S]\) 表示点 \(i\) 可以到达集合 \(S\) 中每一个点的期望,其中 \(i\in S\)
考虑计算出不合法情况减去。如果 \(i\) 不能到达 \(S\) 中所有的点,当且仅当存在 \(S\) 的一个子集 \(T\),使得 \(S\) \ \(T\) 中所有点,与 \(T\) 中所有点之间的边,都是指向 \(S\) \ \(T\) 的。
可以 \(O(2^nn^2)\) 预处理出 \(g[i][S]\) 表示点 \(i\)\(S\) 中连边都是连向 \(S\) 的期望。
然后考虑转移。枚举 \(S\) 的子集 \(T\),把不合法情况的期望加起来,最后用 \(1\) 减去即可。

\[f[i][s]=1-\sum_{T\subseteq S\ \&\ i\notin T}\left(f[i][S-T]\times \prod_{j\in S\ \&\ j\notin T}g[j][T]\right) \]

最后把每一个点的期望加起来即可。
时间复杂度 \(O(3^nn^2)\)

代码

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;

const int N=15,M=(1<<14),V=2000010,MOD=1e9+7;
int n,lim,ans,a[N];
ll inv[V],f[N][M],g[N][M];

void init()
{
	inv[1]=1;
	for (int i=2;i<V;i++)
		inv[i]=-1LL*(MOD/i)*inv[MOD%i]%MOD;
}

int main()
{
	init();
	scanf("%d",&n);
	for (int i=1;i<=n;i++)
		scanf("%d",&a[i]);
	lim=(1<<n);
	for (int s=1;s<lim;s++)
		for (int i=1;i<=n;i++)
			if (!(s&(1<<i-1)))
			{
				g[i][s]=1;
				for (int j=1;j<=n;j++)
					if (s&(1<<j-1))
						g[i][s]=g[i][s]*a[j]%MOD*inv[a[i]+a[j]]%MOD;
			}
	for (int s=1;s<lim;s++)
		for (int i=1;i<=n;i++)
			if (s&(1<<i-1))
			{
				f[i][s]=1;
				for (int t=s;t;t=(t-1)&s)
					if (!(t&(1<<i-1)))
					{
						ll res=f[i][s^t];
						for (int j=1;j<=n;j++)
							if (s&(1<<j-1) && !(t&(1<<j-1)))
								res=res*g[j][t]%MOD;
						f[i][s]=(f[i][s]-res)%MOD;
					}
			}
	for (int i=1;i<=n;i++)
		ans=(ans+f[i][lim-1])%MOD;
	cout<<(ans+MOD)%MOD;
	return 0;
}
posted @ 2021-10-13 11:12  stoorz  阅读(65)  评论(0编辑  收藏  举报