F - 期望

F - 期望

题意

你有 \(n\) 个开关,每个开关进行操作需要 \(t_i\) 的时间,有 \(\frac{a_i}{b_i}\) 的概率可以打开,剩下的概率会导致全部开关关闭,求开启所有开关的期望时间。

思路

很容易想到先搞出期望 DP 转移方程,然后就可以贪心地唯一确定操作开关的顺序,即使不幸失败导致所有开关关闭了也依然按照这个顺序依次尝试所有开关。

\(p_i=\frac{a_i}{b_i}\)

\(f_i\) 表示开启了前 \(i\) 个开关期望时间。

基本上所有的期望 DP 都可以转化成有向图随机游走问题。

可以建出有向图,\(0\)\(p_1\) 的概率走到 \(1\),有 \(1-p_1\) 的概率走回 \(0\)\(i\)\(p_{i+1}\) 的概率走到 \(i+1\),有 \(1-p_{i+1}\) 的概率走到 \(0\)。终止状态是 \(n\)

对于有向图随机游走问题,一般从终态倒推。\(f_n=0\)

那么有:

\[f_i=p_{i+1}f_{i+1}+(1-p_{i+1})f_0+t_{i+1}\\ \]

高斯消元的复杂度是 \(O(n^3)\),然而实际上我们并不需要高斯消元,观察到这个式子的形式很优美,可以这么做:

\[f_n=0\\ f_{n-1}=(1-p_n)f_0+t_n\\ f_{n-2}=p_{n-1}f_{n-1}+(1-p_{n-1})f_0+t_{n-1}\\ f_i=k_i f_0+b_i\\ \]

最后有:

\[f_0=\frac{b_0}{1-k_0} \]

这种方法被称为主元法,这里是以 \(f_0\) 为主元,将所有的方程使用 \(f_0\) 表示,最后推出 \(f_0\) 的表示然后移项算出 \(f_0\) 的值。

突破口在于观察到如果把 \(f_0\) 看做常量,剩下的元素都可以倒序递推得到。

然后如何确定开关的顺序呢?

采用贪心,假设只有 \(x,y\) 两个灯,比较两种相对顺序哪个更优。

\[f_2=0\\ f_1=(1-p_2)f_0+t_2\\ \begin{aligned} f_0&=p_1f_1+(1-p_1)f_0+t_1\\ &=p1(1-p_2)f_0+p_1t_2+(1-p_1)f_0+t_1\\ &=\frac{p_1t_2+t_1}{p_1p_2} \end{aligned} \]

\[\begin{aligned} (x,y)& <(y,x)\\ \frac{p_xt_y+t_x}{p_xp_y}& < \frac{p_yt_x+t_y}{p_xp_y}\\ p_xt_y+t_x & < p_yt_x+t_y\\ t_x-p_yt_x & < t_y-p_xt_y\\ \frac{t_x}{1-p_x} & < \frac{t_y}{1-p_y} \end{aligned} \]

因此把所有开关按照 \(\frac{t}{1-p}\) 从小到大排序即可。

时间复杂度 \(O(n\log n)\)

code

代码十分好写,注意排序需要使用浮点数比较,因为取模后大小关系会改变。

#include<bits/stdc++.h>
// #define LOCAL
#define sf scanf
#define pf printf
#define rep(x,y,z) for(int x=y;x<=z;x++)
#define per(x,y,z) for(int x=y;x>=z;x--)
using namespace std;
typedef long long ll;
const int N=2e5+7,mod=998244353;
ll add(ll a,ll b) { return a+b>=mod?a+b-mod:a+b; }
ll ksm(ll a,ll b=mod-2) {
	ll s=1;
	while(b) {
		if(b&1) s=s*a%mod;
		a=a*a%mod;
		b>>=1;
	}
	return s;
}
struct node {
	ll t,a,b;
}x[N];
int n;
bool cmp (node x,node y) {
	return 1.0*x.t*(1-1.0*y.a/y.b) < 1.0*y.t*(1-1.0*x.a/x.b);
}
struct line {
	ll k,b;
}f[N];
line operator * (ll a,line b) {
	return {a*b.k%mod,a*b.b%mod};
}
line operator + (line a,line b) {
	return {add(a.k,b.k),add(a.b,b.b)};
}
ll ans;
int main() { 
    #ifdef LOCAL
    freopen("in.txt","r",stdin);
    freopen("my.out","w",stdout);
    #endif
	sf("%d",&n);
	rep(i,1,n) sf("%lld%lld%lld",&x[i].t,&x[i].a,&x[i].b);
	sort(x+1,x+n+1,cmp);
	rep(i,1,n) x[i].a=x[i].a*ksm(x[i].b)%mod;
	f[n]={0,0};
	per(i,n-1,0) {
		f[i]=x[i+1].a*f[i+1]+(line){add(1,mod-x[i+1].a),x[i+1].t};
	}
	ans=f[0].b*ksm(add(1,mod-f[0].k))%mod;
	pf("%lld\n",ans);
}
posted @ 2024-10-18 20:30  liyixin  阅读(10)  评论(0编辑  收藏  举报