zoukankan      html  css  js  c++  java
  • 概率期望小记

    概率期望小结

    基础知识:概率,期望,这个博主也是我。

    一个小技巧,求方差的期望时,有特殊的方法:

    (n=|Omega|)

    [V(X)=frac 1n sum_{Ain Omega} (X(A)-E(X)^2) ]

    [=frac 1n (sum_{Ain Omega} X(A)^2+sum_{Ain Omega} E(X)^2 -2E(X)sum_{Ain Omega}X(A)) ]

    [=E(X^2)+E(X)E(X)-2E(X)E(x) ]

    [=E(X^2)-E(X)^2 ]

    概率期望的核心知识点就在于求解概率/期望上,这里讲一些期望的常见求解方法:

    一、

    直接利用定义正向DP得到随机变量所有取值的出现概率,再利用定义计算期望

    通常适用于随机的变量的取值有限的情况。

    例:洛谷P3600 随机数生成器

    显然答案的最大值一定 (in[1,x]),于是我们可以转化为概率问题:

    [ E(ans)=sum_{i=1}^{x} iP(ans=i)=sum_{i=1}^{x} i(P(ansge i)-P(ans ge{i-1})) ]

    [E(ans)=sum_{i=1}^{x} P(ansge i)=x-sum_{i=1}^{x} P(ans<i) ]

    考虑求解 (P(ans<a)),也就是所有区间中都有 (<a) 的数的概率。

    首先如果一个区间完全包含了另一个区间,那么只要满足后者,前者一定满足,于是可以去掉前者。

    将剩下的区间按左端点从小到大排序,右端点一定单调递增。

    此时再考虑任意一个点 (i),如果选择这个点的 (w_i<a),这样的选择发生的概率为 (p=dfrac{i-1}{x}) ,那么它会对所有覆盖它的区间打上标记,排序后这样的区间一定是连续的一段 ([l_i,r_i])

    (f_i) 表示选择了 (i) 这个点,并且第 (1-r_i) 个区间都已经被打上标记了。枚举上一个选择的点(j),转移即为:

    [ f_i=psum_{j=0}^{i-1}[r_jge l_i-1]f_j(1-p)^{i-j-1} ]

    思路就是上一个选择的 (j) 最多能覆盖到 (r_j),而新选择的 (i) 覆盖 ([l_i,r_i]),那么中间不能有空缺的部分,同时 ([j+1,i-1]) 的数都不应该 (<a)

    直接递推是 (mathcal O(n^2)) 的,可以双指针优化到 (mathcal O(n))

    于是 (P(ans<i)=sum_{i=1}^{n} [r_i=m]f_i(1-p)^{n-i})

    总复杂度 (mathcal O(n^2))

    view code
        #include<bits/stdc++.h>
        using namespace std;
        const int mod=666623333;
        const int N=2010;
        int n,x,m,cnt,flag[N],l[N],r[N],f[N];
        struct query{
        	int l,r;
        }q[N],a[N];
        inline bool cmp(query x,query y){return (x.l^y.l)?x.l<y.l:x.r>y.r;}
        inline int dec(int x,int y){return (x-y<0)?x-y+mod:x-y;}
        inline void upd(int &x,int y){x=(x+y>=mod)?x+y-mod:x+y;}
        inline void dwn(int &x,int y){x=(x-y<0)?x-y+mod:x-y;}
        inline int ksm(int x,int y){
        	int ret=1;
        	for(;y;y>>=1,x=1ll*x*x%mod) if(y&1) ret=1ll*ret*x%mod;
        	return ret;
        }
        int pwp[N],pwnp[N],pwiv[N];
        int main(){
        	scanf("%d%d%d",&n,&x,&m);
        	for(int i=1;i<=m;++i) scanf("%d%d",&q[i].l,&q[i].r);	
        	sort(q+1,q+m+1,cmp);
        	for(int i=1;i<=m;++i){
        		while(cnt&&q[i].r<=a[cnt].r) cnt--;
        		a[++cnt]=q[i];
        	}
        	int ans=x,iv=ksm(x,mod-2);
        	for(int i=1;i<=n;++i){
        		r[i]=0;l[i]=1;
        		while(r[i]<cnt&&a[r[i]+1].l<=i) ++r[i];
        		while(l[i]<=r[i]&&a[l[i]].r<i) ++l[i];
        	}
        	for(int i=1;i<=x;++i){
        		int p=1ll*(i-1)*iv%mod,np=dec(1,p),iv=ksm(np,mod-2); 
        		pwp[0]=pwnp[0]=pwiv[0]=1;
        		for(int j=1;j<=n;++j){
        			pwp[j]=1ll*pwp[j-1]*p%mod,pwnp[j]=1ll*pwnp[j-1]*np%mod;
        			pwiv[j]=1ll*pwiv[j-1]*iv%mod;
        		}
        		f[0]=1;int sum=1,now=0;
        		for(int j=1;j<=n;++j){
        			while(now<j&&r[now]<l[j]-1) dwn(sum,1ll*f[now]*pwiv[now]%mod),++now;
        			f[j]=1ll*pwnp[j-1]*p%mod*sum%mod;
        			upd(sum,1ll*f[j]*pwiv[j]%mod);
        			if(r[j]==cnt) dwn(ans,1ll*f[j]*pwnp[n-j]%mod);
        		}
        	}
        	printf("%d
    ",ans);
        	return 0;
        }
    

    二、

    直接使用期望 (dp),设 (f_i) 表示当前状态是 (i),从当前状态到末状态这部分过程的答案期望。通常末状态的 (f) 为0,然后从末状态往初状态倒着递推。

    例:[NOI2012]迷失游乐园

    考虑求出从每个点出发的路径边权和期望 (ans_i),然后得到答案。

    对树上的情况,每个点出发的路径有两种,第一步先走到父亲,或者第一步走到某个儿子。分别用 (f_u)(g_u) 来表示这两种情况的答案.

    (g) 的递推显然:末状态就是叶子结点 (g_u=0)

    其他点满足(g_u=frac {1}{|son_u|} sum_{vin son_u}g _v+w_{u,v})

    (v)(u) 的父亲,那么

    [f[u]=w_{u,v}+frac{g_v*|son_v|+f_v-(g_u+w_{u,v})}{|son_v|+1-1} ]

    末状态为 (u) 为根时 (f_u=0)。用 (f)(g) 推出 (ans) 是容易的。

    对于基环树的情况,仍然考虑用 (f)(g) 来计算 (ans)。对于环上的点,我们将向上走定义为在环上走,向下走定义为朝以自己为根的那棵树走。

    (g) 的求法不变,但用同样的求法求 (f) 时,树上的点可以照搬,但环上的点就会循环调用,因此特殊处理一下。

    在环上走,一定是沿顺时针或逆时针走到某个点 (i) 后停止或者往 (i)的 子树走,直接推就可以了。求出环上的 (f) 后,再用之前的方法算树的 (f) 就没了。

    view code
    #include<bits/stdc++.h>
    using namespace std;
    const int N=1e5+10;
    int n,m,cnt,first[N];
    struct node{
    	int u,v,w,nxt;
    }e[N<<1];
    inline void add(int u,int v,int w){e[++cnt].v=v;e[cnt].w=w;e[cnt].nxt=first[u];first[u]=cnt;}
    int fa[N],son[N];
    double down[N],up[N],ans[N];
    bool vis[N]; 
    int rt,fr[N];
    int stk[N],top,L[N],R[N],fw[N],pre[N],nxt[N]; 
    inline void findloop(int u,int f){
    	fr[u]=f;
    	vis[u]=1;
    	if(top) return ;
    	for(int i=first[u];i;i=e[i].nxt){
    		int v=e[i].v;
    		if(v==f) continue;
    		if(vis[v]){
    			rt=v;
    			top=0;L[v]=R[u]=e[i].w;
    			while(u!=v){
    				stk[++top]=u;L[u]=R[fr[u]]=fw[u];
    				vis[u]=1,u=fr[u];
    			}
    			vis[v]=1;stk[++top]=v;
    			return ;
    		} 
    		fw[v]=e[i].w;findloop(v,u);
    		if(top) return;
    	}
    }
    inline void dfs_down(int u,int f){
    	for(int i=first[u];i;i=e[i].nxt){
    		int v=e[i].v;
    		if(v==f||vis[v]) continue;
    		son[u]++;fa[v]=1;
    		dfs_down(v,u);
    		down[u]+=down[v]+e[i].w;
    	}
    	if(!son[u]) return ;
    	down[u]=down[u]/son[u];
    }
    inline void dfs_up(int u,int f,int w){
    	if(!f) up[u]=0;
    	else{
    		if(fa[f]+son[f]==1) up[u]=w;
    		else up[u]=w+(up[f]*fa[f]+down[f]*son[f]-(down[u]+w))/(fa[f]+son[f]-1);	
    	}
    	for(int i=first[u];i;i=e[i].nxt){
    		int v=e[i].v;
    		if(v==f||vis[v]) continue;
    		dfs_up(v,u,e[i].w);
    	}
    }
    int main(){
    	scanf("%d%d",&n,&m);
    	for(int i=1,u,v,w;i<=m;++i)
    		scanf("%d%d%d",&u,&v,&w),add(u,v,w),add(v,u,w);
    	if(m==n){
    		findloop(1,0);
    		memset(vis,0,sizeof(vis));
    		for(int i=1;i<=top;++i) vis[stk[i]]=1,pre[i]=i==1?top:i-1,nxt[i]=i==top?1:i+1;
    		for(int i=1;i<=n;++i) fa[i]=1;
    		for(int i=1;i<=top;++i){
    			int u=stk[i];fa[u]=2;
    			dfs_down(u,0);
    		}
    		for(int i=1;i<=top;++i){
    			int u=stk[i];
    			double p=0.5;
    			for(int j=pre[i];j!=i;j=pre[j]){
    				int v=stk[j];
    				if(pre[j]==i) up[u]+=p*(down[v]+L[v]);
    				else up[u]+=p*((down[v]*son[v])/(son[v]+1)+L[v]);
    				p*=1.0/(son[v]+1);
    			}
    			p=0.5;
    			for(int j=nxt[i];j!=i;j=nxt[j]){
    				int v=stk[j];
    				if(nxt[j]==i) up[u]+=p*(down[v]+R[v]);
    				else up[u]+=p*((down[v]*son[v])/(son[v]+1)+R[v]);
    				p*=1.0/(son[v]+1);
    			}
    			for(int t=first[u];t;t=e[t].nxt) if(!vis[e[t].v]) dfs_up(e[t].v,u,e[t].w);
    		}
    	}
    	else{
    		dfs_down(1,0);
    		dfs_up(1,0,0);
    	}	
    	double ret=0;	
    	for(int u=1;u<=n;++u){
    		ans[u]=(down[u]*son[u]+up[u]*fa[u])/(son[u]+fa[u]);
    		ret+=ans[u];
    	}
    	printf("%.5lf
    ",ret/n);
    	return 0;
    }
    

    三、

    有些时候 (f) 的递推求解会互相调用对方,可以列出方程后解方程得到递推式完成。

    这个方程可能比较简单,可以直接解出,然后就变成普通的递推了。也可能需要使用高斯消元或模拟高斯消元来解。

    模拟高斯消元,就是利用方程矩阵的特殊形式手动消元优化复杂度。

    例:[六省联考2017]分手是祝愿

    首先,一个局面的最优策略是从后往前扫,如果当前位置为 (1)就进行操作。

    这是因为前面的位置的操作一定不会再影响到这个位置了,所以必须进行这一次操作。同时也可以看出,对于任何一个局面,它的操作序列是固定的。

    根据上面的策略,我们可已经每一个局面转化为1个 (01) 串。其中第 (i) 位表示是否要对这一位进行操作。

    那么这个局面的最优操作次数就是 (1)的个数。因此,我们只关心一个 (01) 串中有多少个 (1) 而不关心这个串具体情况。

    (f[i]) 表示串中有 (i)(1) 时的期望要走多少次才能变为只有 ((i-1))(1) 的情况。

    (frac in) 的概率选到 (1) 成功转化,也有 (frac {n-i}n)的概率变为 ((i+1))(1),还需要再进行 (f[i+1]+f[i]) 次操作

    [f[i]=frac{i}{n}+frac{n-i}{n}(1+f[i+1]+f[i]) ]

    解方程得到 (f[i]=frac{(n-i)f[i+1]+n}{i})(ans=sum_{i=k+1}^{cnt}f[i]+k),其中 (cnt)为初始局面最优操作次数。

    view code
    #include<bits/stdc++.h>
    using namespace std;
    const int mod=100003;
    const int N=1e5+10;
    int n,k,w[N],f[N],inv[N];
    vector<int> d[N];
    int main(){
    	scanf("%d%d",&n,&k);
    	for(int i=1;i<=n;++i) scanf("%d",&w[i]);
    	int ans=0;
    	for(int i=1;i<=n;++i)
    		for(int j=i<<1;j<=n;j+=i) d[j].push_back(i);
    	for(int i=n;i>=1;--i){
    		if(w[i]){
    			ans++;
    			w[i]^=1;
    			for(int v:d[i]) w[v]^=1;
    		}
    	}
    	if(ans>k){
    		f[n+1]=0;
    		int ret=k;
    		inv[0]=inv[1]=1;
    		for(int i=2;i<=n;++i) inv[i]=1ll*(mod-mod/i)*inv[mod%i]%mod;
    		for(int i=n;i>=k+1;--i){
    			f[i]=1ll*(n+1ll*(n-i)*f[i+1]%mod)%mod*inv[i]%mod;
    			if(i<=ans) ret+=f[i];
    		}
    		ans=ret;
    	}
    	for(int i=1;i<=n;++i) ans=1ll*ans*i%mod; 
    	printf("%d
    ",ans);
    	return 0;
    }
    

    例:CF1349D

    (E_i) 表示游戏结束时 (i) 拥有所有饼干的情况的期望步数。则 (ans=sum_{i=1}^{n}E_i)

    直接算 (E_i) 非常难算,考虑 (F_i) 表示一个新的仅在 (i) 拥有所有饼干时才结束的游戏的期望步数,(P_i) 表示游戏结束时 (i)拥有所有饼干的概率。(C) 表示 (i) 拥有所有饼干后期望经过多少步才能使 (j) 拥有所有饼干。

    显然对于所有 (i,j) 这个步数相同。

    那么不难从 (F_i) 得到 (E_i),考虑枚举游戏终止时拥有所有饼干的人,考虑枚举游戏终止时拥有所有饼干的人:

    [E_i=F_i-sum_{j=1,j ot=i}^{n}(P_jC+E_j) ]

    [sum_{i=1}^{n}E_i=F_i-C*sum_{j=1,j ot=i}^{n}P_j=ans ]

    将所有的 (n) 个柿子加起来得到:

    [ n*ans=sum_{i=1}^{n}F_i-C(n-1)sum_{i=1}^{n}P_i=sum_{i=1}^{n}F_i-C(n-1) ]

    再求解 (F_i)(C) 时,我们实际上只关心每个饼干在第 (i) 个人手中还是不在了,

    因此设 (f_i) 表示这个人现在有 (i) 个饼干,期望经过多少步后才能拥有所有饼干。于是 (F_i=f_{a_i},C=f_0)

    对于 (f) 的求解,边界条件为 (f_m=0),容易写出递推式:

    [ f_i=1+frac{m-i}{m}(frac{n-2}{n-1}f_i+frac{1}{n-1}f_{i+1})+frac im f_{i-1}(i>0) ]

    [f_i=1+frac{n-2}{n-1}f_0+frac{1}{n-1} f_1(i=0) ]

    到这里我们的问题就转换为解方程了,直接高斯消元复杂度太高,考虑到这些方程左右两侧所有系数的和都是 (1)

    因此如果令 (g_i=f_i-f_{i+1}),然后将 (f_i) 替换为 (sum_{j=i}^m g_j),那么 (g_{i+1},g_{i+2},dots g_n)都会被抵消掉。

    于是方程就只剩 (g_i)(g_{i-1})了,直接递推即可。

    化简后得到方程为

    [ g_i=frac{i(n-1)g_{i-1}+m(n-1)}{m-i}(i>0) ]

    [g_0=n-1 ]

    代码可能比我的题解还短。

    view code
    #include<bits/stdc++.h>
    using namespace std;
    const int mod=998244353;
    const int N=3e5+10;
    int n,a[N],m,g[N],f[N],inv[N];
    inline int add(int x,int y){return (x+y>=mod)?x+y-mod:x+y;}
    int main(){
    	scanf("%d",&n);
    	for(int i=1;i<=n;++i) scanf("%d",&a[i]),m+=a[i];
    	g[0]=n-1;
    	inv[0]=inv[1]=1;for(int i=2;i<=max(m,n);++i) inv[i]=1ll*(mod-mod/i)*inv[mod%i]%mod;
    	for(int i=1;i<m;++i)
    		g[i]=1ll*add(1ll*i*(n-1)%mod*g[i-1]%mod,1ll*m*(n-1)%mod)*inv[m-i]%mod;
    	for(int i=m-1;i>=0;--i) f[i]=add(f[i+1],g[i]);
    	int ans=0;
    	for(int i=1;i<=n;++i) ans=add(ans,f[a[i]]);
    	ans=(ans-1ll*f[0]*(n-1)%mod+mod)%mod;
    	printf("%d
    ",1ll*ans*inv[n]%mod);
    	return 0;
    }
    

    四、

    利用概率生成函数求解。

    对于任意一个取值在非负整数集上的离散随机变量(X),其概率生成函数为:

    [ F(z)=sum_{i=0}^{infty}P(X=i)z^i ]

    重要性质:
    1.(F(1)=1)
    2.(E(X)=F'(1)):(F'(z)=sum_{i=0}^{infty}(i+1)P(X=i+1)z^i),代入(1)后正好是期望的定义。
    3.(E(X^{underline{k}})=F^{(k)}(1))
    4.(E(X^k)=sum_{i=0}^{k}left{egin{matrix}k\iend{matrix} ight}F^{(i)}(1))

    例:[ZJOI2013]抛硬币

    定义 (f_i) 表示扔了 (i) 次后游戏结束的概率,(g_i) 表示扔了 (i) 次后游戏没有结束的概率。

    (f)(g) 建立生成函数 (F)(G) ,此时(F)(PGF)满足上面的性质,但(G)并不是,答案是(F'(1))

    可以得到方程:(F(x)+G(x)=xG(x)+1)

    考虑在一个未完成的序列后增加一个(A)(设长为(L)),那么游戏一定会结束,但有可能在第(i)个位置提前结束,

    那么此时(A[1-i])必须是一个(border)并且此时之后扔出的(L-i)次的影响应当消去,求出(a_i)表示(A[1dots i])是否是(border)

    (G(x)P(A)=sum_{i=1}^{L}F(x)P(A[i+1dots n])a_i),其中(P(S))(S)整个串连续出现的概率。

    解方程就完了。

    view code
    这题要写高精度,太恶心了,所以code咕掉了
    

    例:[SDOI2017]硬币游戏

    设这(n)个串为(A_1dots A_n)

    我们依然使用套路,定义(f_{i,j})表示第(i)个串在扔(j)次硬币后结束游戏的概率,以及(g_i)表示扔(i)次硬币游戏没有结束的概率。

    以此建出生成函数(F_i(x))(G(x)),那么答案就是(F_i(1))

    显然有一个方程:(sum_{i=1}^{n}F_i(1)=1)

    依然考虑在未完成的字符串后直接添加第(i)个字符串,但此时我们不只要考虑是否提前结束了游戏,还有考虑是哪个字符串结束的。

    定义(a_{i,j,k}),为(1)当且仅当(A_i[1dots k]=A_j[n-k+1dots n]),否则为(0),这个显然可以用(hash mathcal O(n^3))求出。

    得到方程如下:

    [frac{G(x)x^m}{2^m}=sum_{j=1}^{n}sum_{k=1}^{m} a_{i,j,k}F_j(x)frac{x^{m-k}}{2^{m-k}} ]

    (x=1)代入,于是最终我们得到了(n)个关于(F_i(1))(G(1))的方程,加上一开始那个一共(n+1)个,可以使用高斯消元完成,复杂度(mathcal O(n^3))

    view code
    #include<bits/stdc++.h>
    using namespace std;
    const int N=310;
    const int mod=1e9+7;
    const double eps=1e-10;
    char s[N][N];
    int n,m;
    int a[N][N][N],pw[N],id[N];
    double c[N][N],p[N],ans[N];
    int hsh[N][N];
    inline int dec(int x,int y){return (x-y<0)?x-y+mod:x-y;}
    inline int gethsh(int i,int l,int r){
    	return dec(hsh[i][r],1ll*pw[r-l+1]*hsh[i][l-1]%mod);
    }
    inline void init(){
    	pw[0]=1;p[0]=1;
    	for(int i=1;i<=m;++i) pw[i]=(pw[i-1]<<1)%mod,p[i]=p[i-1]*2;
    	for(int i=1;i<=n;++i)
    		for(int j=1;j<=m;++j) hsh[i][j]=((hsh[i][j-1]<<1)+(s[i][j]=='H'))%mod;
    	for(int i=1;i<=n;++i)
    		for(int j=1;j<=n;++j)
    			for(int k=1;k<=m;++k)
    				a[i][j][k]=gethsh(i,1,k)==gethsh(j,m-k+1,m);
    }	
    inline void Gauss(){
    	for(int i=1;i<=n+1;++i){
    		if(c[i][i]>-eps&&c[i][i]<eps){
    			for(int j=i+1;j<=n+1;++j)
    				if(c[j][i]<-eps||c[j][i]>eps){swap(id[i],id[j]),swap(c[i],c[j]);break;}
    		}
    		for(int j=n+2;j>=i;--j) c[i][j]/=c[i][i];
    		for(int j=i+1;j<=n+1;++j)
    			for(int k=n+2;k>=i;--k)
    				c[j][k]-=c[i][k]*c[j][i];
    	}
    	for(int i=n+1;i>=1;--i){
    		c[i][i]=c[i][n+2]/c[i][i];ans[id[i]]=c[i][i];
    		for(int j=i-1;j>=1;--j) c[j][n+2]-=c[j][i]*c[i][i];
    	}
    	for(int i=1;i<=n;++i) printf("%.10lf
    ",ans[i]);
    }
    int main(){
    	scanf("%d%d",&n,&m);
    	for(int i=1;i<=n;++i) scanf("%s",s[i]+1);
    	init(); 
    	for(int i=1;i<=n+1;++i) id[i]=i;
    	for(int i=1;i<=n;++i){
    		c[i][n+1]=-1;
    		for(int j=1;j<=n;++j)
    			for(int k=1;k<=m;++k)
    				if(a[i][j][k]) c[i][j]+=p[k];		
    	}
    	for(int i=1;i<=n;++i) c[n+1][i]=1;c[n+1][n+2]=1;
    	Gauss();
    	return 0;
    }
    
  • 相关阅读:
    PHP面向对象——类
    PHP强大的数组函数
    php学习资源
    版本管理(二)之Git和GitHub的连接和使用
    版本管理(一)之Git和GitHub的区别(优点和缺点)
    (win10)Wamp环境下php升级至PHP7.2
    wamp3.1.0 X64下载链接
    4.总结近5周以来的github上的工作情况,以图表方式分析你小组的工作情况、存在的问题及解决的方案。(尤心心)
    四则运算需求分析和功能实现--杨宇杰
    1.对四则运算软件需求的获取方式进行实践,例如使用调查问卷访问相关关系人等。
  • 原文地址:https://www.cnblogs.com/tqxboomzero/p/14646512.html
Copyright © 2011-2022 走看看