期望题乱做

P6154 游走

给定一个有向无环图,求其中一条路径长度的期望。


对于一条路径,\(E=\dfrac{sum}{cnt}\)\(sum\) 为长度总和, \(cnt\) 是路径个数。

考虑记忆化搜索。令 \(f_i\) 表示 \(i\) 开始的路径长度和, \(g_i\) 表示路径条数。

那么 \(f_i=\sum f_j+g_j,g_i=1+\sum g_j\) (加上自己到自己)

\(ans=\dfrac{\sum f_i}{\sum g_i}\)

void dp( int u )
{
	if ( g[u] ) return;
	g[u]=1;
	for ( int i=head[u]; i; i=e[i].nxt )
	{
		int v=e[i].to; dp( v );
		g[u]=(g[u]+g[v])%mod;
		f[u]=(f[u]+f[v]+g[v])%mod;
	}
}

int main()
{
	scanf( "%d%d",&n,&m );
	for ( int i=1,u,v; i<=m; i++ )
		scanf( "%d%d",&u,&v ),add( u,v );
	
	memset( g,0,sizeof(g) ); memset( f,0,sizeof(f) );
	for ( int i=1; i<=n; i++ )
		if ( !g[i] ) dp(i);
	ll s1=0,s2=0;
	for ( int i=1; i<=n; i++ )
		s1=(s1+f[i])%mod,s2=(s2+g[i])%mod;
	
	printf( "%lld\n",s1*power(s2,mod-2,mod)%mod );
}

P1850 换教室

\(n\) 节课程,如果申请通过则在 \(d_i\) 上课,否则在 \(c_i\) ,第 \(i\) 节课的通过概率是 \(k_i\) 。每个人至多选择 \(m\) 门课程申请。

学校为 \(v\)\(e\) 边的无向图,每条路有消耗的体力值,每次下课就选择最短路前往下一个教室。求耗费体力值的总和的最小期望值。


\(dp[i][j][0/1]\) 表示前 \(i\) 堂课申请了 \(j\) 次,第 \(i\) 堂课申请或不申请的最小期望花费。分类讨论。

  • 如果第 \(i\) 堂课不申请:

    1. \(i-1\) 也不申请,那么期望花费是: \(t1=f[c[i-1]][c[i]]\)
    2. 申请,\(t2=k[i-1]\times f[d[i-1]][c[i]]+(1-k[i-1])\times f[c[i-1]][c[i]]\)

    结合起来就是:\(dp[i][j][0]=min(dp[i-1][j][0]+t1,dp[i-1][j][1]+t2)\)

  • \(i\) 堂课申请:

    1. \(i-1\)不申请 \(t1=k[i]\times f[c[i-1]][d[i]]+(1-k[i])\times f[c[i-1]][c[i]]\)

    2. \(i-1\)也申请

      • 都成功:\(t2=k[i-1]\times k[i]\times f[d[i-1]][d[i]]\)

      • \(i-1\)成功:\(t3=k[i-1]\times (1-k[i])\times f[d[i-1]][c[i]]\)

      • \(i\) 成功:\(t4=(1-k[i-1])\times k[i]\times f[c[i-1]][d[i]]\)

      • 没成功:\(t5=(1-k[i-1])\times (1-k[i])\times f[c[i-1]][c[i]]\)

    总结一下就是:\(dp[i][j][1]=min( dp[i-1][j-1][0]+t1,dp[i-1][j-1][1]+t2+t3+t4+t5\)

边界:\(dp[1][0][0]=0,dp[1][1][1]=0\)

#include <bits/stdc++.h>
using namespace std;
const int N=2010,V=310;
const double inf=1e9+7;
double p[N],dis[V][V],f[N][N][2];
int c[N],d[N],n,m,v,e;

void get_dis()
{
	for ( int k=1; k<=v; k++ )
	 for ( int i=1; i<=v; i++ )
	  for ( int j=1; j<=v; j++ )
	  	dis[i][j]=dis[j][i]=min( dis[i][j],dis[i][k]+dis[k][j] );
}

int main()
{
	scanf( "%d%d%d%d",&n,&m,&v,&e );
	for ( int i=1; i<=n; i++ )
		scanf( "%d",&c[i] );
	for ( int i=1; i<=n; i++ )
		scanf( "%d",&d[i] );
	for ( int i=1; i<=n; i++ )
		scanf( "%lf",&p[i] );
	init();
	for ( int i=1,u,v,w; i<=e; i++ )
		scanf( "%d%d%d",&u,&v,&w ),dis[u][v]=dis[v][u]=min( dis[u][v],(double)w );
	get_dis();
	
	for ( int i=2; i<=n; i++ )
	 for ( int j=0; j<=min(i,m); j++ )
	 {
	 	double t1=dis[c[i-1]][c[i]],t2=p[i-1]*dis[d[i-1]][c[i]]+(1-p[i-1])*dis[c[i-1]][c[i]];
	 	f[i][j][0]=min( f[i-1][j][0]+t1,f[i-1][j][1]+t2 );
	 	if ( j>0 )
	 	{
	 		t1=p[i]*dis[c[i-1]][d[i]]+(1-p[i])*dis[c[i-1]][c[i]];
	 		t2=p[i-1]*p[i]*dis[d[i-1]][d[i]];
	 		double t3=p[i-1]*(1-p[i])*dis[d[i-1]][c[i]];
	 		double t4=(1-p[i-1])*p[i]*dis[c[i-1]][d[i]];
	 		double t5=(1-p[i-1])*(1-p[i])*dis[c[i-1]][c[i]];
	 		f[i][j][1]=min( f[i-1][j-1][0]+t1,f[i-1][j-1][1]+t2+t3+t4+t5 );
		}
	 }
	
	double ans=inf;
	for ( int i=0; i<=m; i++ )
		ans=min( f[n][i][0],min( f[n][i][1],ans ) );
	printf( "%.2lf\n",ans );
	
	return 0;
}

P6059 纯粹容器

\(n\) 个容器,每个强度为 \(a_i\) (互不相同)\(n-1\) 轮操作,每次等概率选两个位置相邻且未被击倒的容器决斗,强度小的容器会被击倒并移出队列。求每个容器存活轮数的期望 $\mod 998244353. $


首先特判,对于最大的 \(a_i\) ,答案显然是 \(n-1\) (不特判最小的原因是等概率选未必会选到它)。枚举每一个 \(a_i\) ,找到距离 \(a_i\) 最近且大于它的 \(a_l,a_r\) ,显然 \(a_i\) 被击倒只有 \([l,i] or[i,r]\) 之间的容器全部决斗。

枚举 \(a_i\) 在第 \(j\) 轮被淘汰,概率为 \(p_{i,j}\) ,那么期望轮数就是 \(\sum_{1\leq j<n} p_{i,j}\times(j-1)\)

\(calc(i,j)\) 表示 n-1 轮决斗中选出 \(i\) 轮全部在前 \(j\) 轮的概率。显然有 \(calc(i,j)=\dfrac{j!(n-1-i)!}{(j-i)!(n-1)!}\)

考虑怎么求 \(p_{i,j}\) ,设 \(g_{i,j}\) 表示 \(a_i\) 在前 \(j\) 轮就被淘汰的概率, \(p_{i,j}=g_{i,j}-g_{i,j-1}\)

对于 \(g_{i,j}\) ,要么被左边淘汰,要么被右边淘汰,容斥得到:

\[g_{i,j}=calc(i-l,j)+calc(r-i,j)-calc(r-l,j) \]

(被左边淘汰+被右边淘汰+前 \(j\) 轮淘汰了两次)

特判:如果某一边没有比它大的容器,那么只有一边淘汰,就是 \(calc(i-l,j)\) 或者 \(calc(r-i,j)\)

ll calc( int i,int j )
{
        if ( i>j ) return 0;
        return fac[j]*fac[n-1-i]%mod*inv[j-i]%mod*inv[n-1]%mod;
}

int main()
{
        scanf( "%d",&n );
        for ( int i=1; i<=n; i++ )
                scanf( "%d",&a[i] );
        
        init(); memset( g,0,sizeof(g) );
        for ( int i=1; i<=n; i++ )
        {
                int l=i-1,r=i+1;
                while ( l>=1 && a[l]<a[i] ) l--;
                while ( r<=n && a[r]<a[i] ) r++;
                if ( l==0 && r==n+1 ) { ans[i]=n-1; continue; }
                if ( l==0 )
                {
                        for ( int j=1; j<n; j++ )
                                g[i][j]=calc( r-i,j );
                }
                else if ( r==n+1 )
                {
                        for ( int j=1; j<n; j++ )
                                g[i][j]=calc( i-l,j );
                }
                else 
                {
                        for ( int j=1 ;j<n; j++ )
                                g[i][j]=(calc( i-l,j )+calc( r-i,j )-calc( r-l,j )+mod)%mod;
                }
                for ( int j=1; j<n; j++ )
                        (ans[i]+=(g[i][j]-g[i][j-1]+mod)*(j-1)%mod)%=mod;
        }

        for ( int i=1; i<=n; i++ )
                printf( "%lld ",(ans[i]+mod)%mod );
}

P6835 线形生物

起点为 \(1\) ,走到 \(n+1\) 停止,有 \(n\) 条边,每条连接 \((i,i+1)\) ;外加 \(m\) 条返祖边, \((u_i\to v_i)(u_i\ge v_i)\) 。每一步等概率走向一条出边。求总步数的期望 \(E(\delta)\mod 998244353.\)


\(E_{x,x+1}\) 表示 \(x\to x+1\) 的期望步数。那么 \(ans=\sum_{i=1}^n E_{i,i+1}.\)

\(d_x\) 表示 \(x\) 的返祖边数目, \(Edge\) 表示 \(x\) 的返祖边集合。

\[E_{x,x+1}=\dfrac{1}{d_x+1}\times 1+\dfrac{1}{d_x+1}\sum_{(x,y)\in Edge} (E_{y,x+1}+1) \]

\(E_{y,x+1}=\sum_{i=y}^x E_{i,i+1}\) 代入上面的式子,并化简:

\[E_{x,x+1}=1+\dfrac{1}{d_x+1} \sum_{(x,y)\in Edge}\sum_{i=y}^x E_{i,i+1} \]

设一类特殊的 \(E\)\(E_{x,x+1}\)\(f_x\) ,并记 \(s_x=\sum_{i=0}^x f_i\) ,代入上式得到:

\[f_x=1+\dfrac{1}{d_x+1}\sum_{(x,y)\in Edge} (s_x-s_{y-1}) \]

两边都含有项 \(f_x\) ,那么把这一项全部移到等式左边:

\[f_x=1+\dfrac{1}{d_x+1}\sum_{(x,y)\in Edge}(s_{x-1}-s_{y-1})+\dfrac{1}{d_x+1}\times f_x\times d_x \\\\ f_x\times \dfrac{1}{d_x+1}=1+\dfrac{1}{d_x+1}\sum_{(x,y)\in Edge}(s_{x-1}-s_{y-1}) \\\\ f_x=d_x+1+\sum_{(x,y)\in Edge}(s_{x-1}-s_{y-1}) \]

然后维护前缀和即可。时间复杂度 \(O(n).\)

int main()
{
        scanf( "%d%d%d",&id,&n,&m );
        for ( int i=1,u,v; i<=m; i++ )
                scanf( "%d%d",&u,&v ),add( u,v ),d[u]++;
        
        s[0]=0;
        for ( int i=1; i<=n; i++ )
        {
                f[i]=d[i]+1;
                for ( int j=head[i]; j; j=e[j].nxt )
                        (f[i]+=(s[i-1]-s[e[j].to-1]+mod)%mod)%=mod;
                s[i]=(s[i-1]+f[i])%mod;
        }

        printf( "%lld",s[n] );
}

P3802 小魔女帕琪

\(7\) 种晶体,每种可以释放属性 \(i\) 的魔法,有 \(a_i\) 个,每次等概率使用一个,问魔法序列中有 \(7\) 个元素的排列组成的子串个数。


\(s\) 表示当前所剩下的晶体总个数,那么式子就是

\[E=(s-6)P=(s-6)\times \dfrac{7!\times \prod_{i=1}^7a_i}{\prod_{i=s-6}^{s}i} \]

//Author: RingweEH
int main()
{
	double sum=0,ans=1;
	for ( int i=1; i<=7; i++ ) scanf( "%lf",&a[i] ),sum+=a[i];
	for ( int i=1; i<=6; i++ ) ans=ans*a[i]/(sum+1-i)*i;
	ans=ans*a[7]*7.0;
	printf( "%.3lf\n",ans );

	return 0;
}

P4550 收集邮票

\(f[i]\) 表示当前有 \(i\) 种邮票,还需要的次数。那么有

\[f[i]=\dfrac in(f[i]+1)+\dfrac{n-i}{n}(f[i+1]+1) \]

然后来考虑花费。设 \(g[i]\) 表示当前有 \(i\) 种邮票,还需要 \(g[i]\) 的钱得到 \(n\) 种。

\[g[i]=\dfrac in(g[i]+f[i]+1)+\dfrac{n-i}{n}(g[i+1]+f[i+1]+1) \]

化简即可。

这种思想在做这种概率期望题的时候应该还是很常用的。

//Author: RingweEH
const int N=1e4+10;
double f[N],g[N];
int n;

int main()
{
	n=read();
	f[n]=g[n]=0;
	for ( int i=n-1; i>=0; i-- ) f[i]=n*1.0/(n*1.0-i)+f[i+1];
	for ( int i=n-1; i>=0; i-- ) g[i]=i*1.0/(n*1.0-i)*f[i]+g[i+1]+f[i+1]+n*1.0/(n*1.0-i);
	printf( "%.2lf\n",g[0] );

	return 0;
}

P5011 水の造题

有一个长度为 \(n\) 的操作序列,操作为 \(1\sim k\) ,如果两个相邻的操作在序列中相邻(有序),那么权值会额外加上原来两个操作的权值之和,求最后的权值和期望。


我们可以将权值期望分成两部分:单独贡献和组合贡献。

单独贡献很好算,就是 \(\dfrac nk\sum a_i\) .

考虑组合贡献,在 某两个相邻位置出现的概率是 \(\dfrac 1{k^2}\) ,那么出现次数的期望就是 \(\dfrac{n-1}{k^2}\) ,总贡献就是 \(\dfrac{n-1}{k^2}\sum2a_i\) .

//Author: RingweEH
int main()
{
	int n=readMod(); k=readMod(); 
	int s=0; 
	for ( int i=1; i<=k; i++ ) bmod(s+=readMod());
	int t=1ll*2*n%Mod; bmod(t+=Mod-2);
	bmod(t+=1ll*n*k%Mod);
	int d=power(k); d=1ll*d*d%Mod;
	t=1ll*t*d%Mod;
	printf( "%lld\n",1ll*t*s%Mod );

	return 0;
}

P3232 游走

在一张 \(n\)\(m\) 边的图上随机游走,到 \(n\) 停止。对所有边重标号使得路径标号和期望最小。


注意到 \(m\) 的范围完全有 \(n^2\) ,非常的不可做,所以考虑换角度。由于题目要求是对边重标号,而边的经过次数是由两个端点决定的,那么可以考虑统计点的期望经过次数。而直接做是有后效性的,所以要列出方程然后高斯消元。

方程显然不难:

\[f[i]=\sum_{(i,j)\in E}\dfrac{f[j]}{deg[j]}+[i=1],i\neq n,j\neq n \]

消元完了,对于每一条边,有

\[g[i]=\dfrac{f[u]}{deg[u]}+\dfrac{f[v]}{deg[v]},u\neq n,v\neq n \]

然后贪心放边权即可。

//Author: RingweEH
int main()
{
	n=read(); m=read();
	for ( int i=1; i<=m; i++ )
	{
		int u=read(),v=read(); deg[u]++; deg[v]++;
		ed.push_back(make_pair(u,v));
		G[u].push_back(v); G[v].push_back(u);
	}

	for ( int i=1; i<n; i++ )
	{
		a[i][i]=1; a[i][n]=(i==1);
		for ( int j : G[i] ) 
			if ( j^n ) a[i][j]=-1.0/deg[j];
	}
	Gauss(n-1);	
	for ( int i=0; i<ed.size(); i++ )
	{
		int u=ed[i].fir,v=ed[i].sec;
		db t1=a[u][n]/(1.0*deg[u]),t2=a[v][n]/(1.0*deg[v]);
		if ( u^n ) f[i]+=t1;
		if ( v^n ) f[i]+=t2;
	}
	sort( f,f+m );
	db ans=0; for ( int i=0; i<m; i++ ) ans+=f[i]*(m-i);
	printf( "%.3lf\n",ans );

	return 0;
}

P5516 小铃的烦恼

给定 \(n\) 个数,每次随机选择两个 \(a,b\) ,有 \(p_{a,b}\) 的概率使得 \(b\to a\) ,求使得所有数一样的期望次数。


转移概率是骗人的,因为 \(\sum p_{i,j}=n^2\) 所以全是 \(1\) .

\(p[i]\) 表示最终的数的个数有 \(i\) 个时,全部变成最终状态的概率。转移无非两种:多/少了一个,不变,那么有

\[p[i]=\dfrac{i(n-i)}{n(n-1)}(p[i-1]+p[i+1])+\dfrac{n(n-1)-2i(n-i)}{n(n-1)}p[i]=>\dfrac{2i(n-i)}{n(n-1)}p[i]=\dfrac{i(n-i)}{n(n-1)}(p[i-1]+p[i+1]) \]

注意到这就是个等差数列,而 \(p[0]=0,p[n]=1\) ,所以 \(p[i]=\dfrac in\) .

解决了概率就来考虑期望步数。设 \(f[i]\) 表示当前有 \(i\) 个是最终态的情况下到达最终态的期望步数。而转移出去的期望步数是转移出去的概率的倒数,于是有

\[f[i]=\dfrac{n(n-1)}{2i(n-i)}+\dfrac{i-1}{2i}f[i-1]+\dfrac{i+1}{2i}f[i+1] \]

高斯消元。而每个方程组只有三元,直接消元复杂度过高,手动消元即可。

//Author: RingweEH
int main()
{
    scanf( "%s",s+1 ); n=strlen(s+1);

    for ( int i=1; i<=n; i++ ) num[s[i]-'A']++;
    for ( int i=1; i<=n; i++ ) f[i]=n*(n-1)/(2.0*i*(n-i));
    a[1]=1;
    for ( int i=1; i<n; i++ )
    {
        db t1=-(i+1)/(2.0*i),t2=-i/(2.0*(i+1));
        db k=-t2/a[i];
        f[i+1]+=f[i]*k; a[i+1]=1+k*t1;
    }
    f[n]=0;
    for ( int i=n-1; i>=1; i-- )
    {
        db tmp=-(i+1)/(2.0*i);
        f[i]-=f[i+1]*tmp; f[i]/=a[i];
    }

    db ans=0;
    for ( int i=0; i<26; i++ )
        ans+=num[i]*f[num[i]]/(db)n;
    printf( "%.1lf\n",ans );

    return 0;
}

P3239 亚瑟王

题面略。


根据线性性,显然就是求每个卡牌发动概率乘上伤害再相加。注意到有一个特性:每张牌每局只能发动一次,且前面发动一次之后后面的纸牌不会再有机会发动。每张纸牌发动概率只依赖于前面的纸牌,且只依赖发动数量,那么这个就能作为 DP 状态了。

\(f[i][j]\) 表示前 \(i\) 张纸牌,有 \(j\) 张发动的概率。

如果第 \(i\) 张发动,那么发动概率为 \((1-(1-p[i])^{r-j+1})\) ,否则不发动的概率为 \((1-p[i])^{r-j}\) ,方程就是

\[f[i][j]=f[i-1][j-1]\cdot (1-(1-p[i])^{r-j+1})+f[i-1][j]\cdot (1-p[i])^{r-j} \]

然后就能得到发动概率

\[g[i]=\sum_{j=0}^{\min(i-1,r)}f[i-1][j]\cdot(1-(1-p[i])^{r-j}) \]

不知道为什么快读会出问题……

    while ( T-- )
    {
        memset( f,0,sizeof(f) ); memset( g,0,sizeof(g) );

        scanf( "%d%d",&n,&m );
        for ( int i=1; i<=n; i++ ) scanf( "%lf %d",&p[i],&val[i] );

        f[0][0]=1;
        for ( int i=1; i<=n; i++ )
        {
            int lim=min(i,m);
            for ( int j=0; j<=lim; j++ )
            {
                if ( j>0 ) f[i][j]+=f[i-1][j-1]*(1-power(1-p[i],m-j+1));
                f[i][j]+=f[i-1][j]*power(1-p[i],m-j);
            }
        }
        for ( int i=1; i<=n; i++ )
        {
            int lim=min(i-1,m);
            for ( int j=0; j<=lim; j++ )
                g[i]+=f[i-1][j]*(1-power(1-p[i],m-j));
        }

        db ans=0;
        for ( int i=1; i<=n; i++ ) ans+=g[i]*val[i];
        printf( "%.10lf\n",ans );
    }

P3978 概率论

\(n\) 点有根二叉树的叶节点个数期望。


\(f[i]\) 表示 \(i\) 个节点的二叉树个数,\(g[i]\) 表示 \(i\) 个点的所有二叉树的叶节点总数。

\(f[i]\) 不难得到:\(f[i]=\sum\limits_{i=0}^{n-1}f[i]f[n-i-1]\) ,现在考虑怎么计算 \(g[i]\) 。不难想到,一棵 \(n-1\) 点的二叉树能得到 \(n\) 棵不同的 \(n\) 点二叉树(考虑总共有 \(2n-2\) 个儿子,占用了 \(n-2\) 个),而一棵 \(n\) 点二叉树,设有 \(x\) 个叶子,那么就能生成 \(x\)\(n-1\) 点的二叉树,所以每一棵 \(n-1\) 点的二叉树都会被计算 \(n\) 次,每一次对应一个叶子,那么就有 \(g[i]=f[i-1]\cdot n\) .

答案就是 \(\dfrac{g[n]}{f[n]}\) ,带入卡特兰数的通项公式计算即可。

P4284 概率充电器

充电元件构成一棵树,导线有导电概率,是否直接充电有概率,可以间接充电。问进入充电的元件个数期望。


设对于充电元件 \(i\) ,直接充电的概率是 \(q[i]\) ,对于边 \((u,v)\) ,导电的概率是 \(e[u][v]\) 。设 \(p[u]\) 表示 \(u\) 不充电的概率,那么有 \(p[u]=(1-q[u])\prod\limits_{(u,v)\in E}(1-e[u][v]+e[u][v]p[v])\) 。但是这样要高斯消元,而结构又是树形,所以容易想到进行换根处理。

第一遍 DFS 的式子很简单,只要把 \(p\) 的意义改为 “被子树直接/间接充电”,然后令 \(v\in son(u)\) 即可。

考虑换根之后的计算。令最终被充电的概率为 \(g\) ,显然我们需要统计的就是 \(u\) 为根时按照第一个式子算出的答案,所以首先要计算 \(f'\) 表示原树中除了 \(u\) 的子树得到的答案,有 \(f'[u]=g[fa]/(1-e[fa][u]+e[fa][u]f[u])\) ,两部分合起来就有

\[g[u]=f[u](1-e[fa,u]+e[fa][u]\cdot f'[u]) \]

//Author: RingweEH
void DFS1( int u,int fa )
{
	f[u]=1-p[u];
	for ( int i=head[u]; i; i=e[i].nxt )
	{
		int v=e[i].to;
		if ( v==fa ) continue;
		DFS1(v,u);
		f[u]*=(1-e[i].val+e[i].val*f[v]);
	}
}

void DFS2( int u,int fa,int fro )
{
	if ( u==1 ) g[u]=f[u];
	else
	{
		db ff=g[fa]/(1-e[fro].val+e[fro].val*f[u]);
		g[u]=f[u]*(1-e[fro].val+e[fro].val*ff);
	}
	for ( int i=head[u]; i; i=e[i].nxt )
		if ( e[i].to^fa ) DFS2(e[i].to,u,i);
}

int main()
{
	scanf( "%d",&n );
	for ( int i=1; i<n; i++ )
	{
		int u,v,w;
		scanf( "%d%d%d\n",&u,&v,&w );
		db ww=w*0.01;
		Add(u,v,ww); Add(v,u,ww);
	}
	for ( int i=1; i<=n; i++ ) scanf( "%lf",&p[i] ),p[i]*=0.01;
	
	DFS1(1,0); DFS2(1,0,0); 
	
	db ans=0;
	for ( int i=1; i<=n; i++ ) ans+=1-g[i];
	printf( "%.6lf",ans );
	
	return 0;
}

P3211 XOR和路径

求无向图中 \(1\to n\) 的路径权值异或和期望。


看到异或,可以按位计算。设现在是第 \(k\) 位,\(f[i]\) 表示节点 \(i\) 期望的异或和第 \(k\) 位是 \(1\) 的概率,那么有

\[f[i]=\dfrac{1}{deg[u]}\sum_v[val[i]_k=1]?(1-f[v]):f[v] \]

然后再高斯消元就好了。注意处理自环。

//Author: RingweEH
db Gauss( int n )		//return a[1][n+1]
void Work( int p )
{
	memset( a,0,sizeof(a) ); a[n][n]=1;
	for ( int i=1; i<n; i++ )
	{
		a[i][i]+=deg[i];
		for ( int j=head[i]; j; j=e[j].nxt )
			if ( (e[j].val>>p)&1 ) a[i][e[j].to]++,a[i][n+1]++;
			else a[i][e[j].to]--;
	}
	db res=Gauss(n);
	ans+=(1<<p)*res;
}

P2473 奖励关

\(k\) 次机会,可以选择拿或者不拿,一共有 \(n\) 种物品,每次出现某种物品的概率独立,第 \(i\) 种分数为 \(p[i]\) ,且依赖于当前拿的集合包含 \(s[i]\) ,求最优策略下期望分值。


由于有一个前置 \(s[i]\) ,不难想到用状压DP实现;期望倒推,已成习惯,所以设 \(f[i][S]\) 表示在 \(1\sim i-1\) 中拿取的集合为 \(S\)\(i\sim k\) 的最大得分期望。

对于某一个状态 \(p\) ,如果 \(S\) 满足了取 \(p\) 的条件,那么有两种选择:\(f[i+1][S]\)\(f[i+1][S|(1<<p-1)]+P[p]\) (大写表示得分),否则只有一种。直接DP即可。

//Author: RingweEH
for ( int i=k; i>=1; i-- )
	for ( int S=0; S<(1<<n); S++ )
	{
		for ( int p=1; p<=n; p++ )
			if ( ((S&sta[p])==sta[p]) ) f[i][S]+=max(f[i+1][S],f[i+1][S|(1<<p-1)]+c[p] );
			else f[i][S]+=f[i+1][S];
		f[i][S]/=n;
	}

P4561 排序问题

方案的期望等于一次完成的概率的倒数 。变成了数数题。一次排好的概率就是 \(\dfrac{\prod cnt_{a_i}!}{(n+m)!}\) ,期望就是 \(\dfrac{(n+m)!}{\prod cnt_{a_i}!}\) .

然后直接二分 \(cnt\) 然后check,每次先填出现次数最少的。

我也不知道哪里写挂了,define int long long 就过了……

//Author: RingweEH
int Check( int mid )
{
	int res=mid*(R-L-(tr-tl));
	if ( res>m ) return res;
	for ( int i=tl; i<=tr && res<=m; i++ )
		res+=max(mid-cnt[i],0ll);
	return res;
}

signed main()
{
	fac[0]=1;
	for ( int i=1; i<=(N-10); i++ ) fac[i]=1ll*fac[i-1]*i%Mod;
	int T=read();
	while ( T-- )
	{
		n=read(); m=read(); L=read(); R=read();
		int tot=0;
		for ( int i=1; i<=n; i++ )
			a[i]=read(),tmp[++tot]=a[i],cnt[i]=0;
		sort( a+1,a+1+n ); sort( tmp+1,tmp+tot+1 );
		tot=unique(tmp+1,tmp+1+tot)-tmp-1;
		for ( int i=1; i<=n; i++ )
			a[i]=lower_bound(tmp+1,tmp+1+tot,a[i])-tmp,cnt[a[i]]++;
		tl=lower_bound(tmp+1,tmp+1+tot,L)-tmp;
		tr=upper_bound(tmp+1,tmp+1+tot,R)-tmp-1;
		int l=0,r=n+m;
		while ( l<r )
		{
			int mid=(l+r)>>1;
			if ( Check(mid)<=m ) l=mid+1;
			else r=mid;
		}
		
		int ans=power(fac[l-1],R-L-(tr-tl));
		for ( int i=1; i<=tot; i++ )
			if ( i>=tl && i<=tr ) ans=1ll*ans*fac[max(cnt[i],l-1)]%Mod;
			else ans=1ll*ans*fac[cnt[i]]%Mod;
		ans=1ll*ans*power(l,m-Check(l-1))%Mod;
		printf( "%lld\n",1ll*fac[n+m]*inv(ans)%Mod );
	}

	return 0;
}

P3830 随机树

从根节点开始,每次随机将一个叶节点展开成挂着两个新的叶节点,求叶节点平均深度的数学期望和树深度的期望。


对于第一问:

将所有叶节点的深度 “基准” 设为当前扩展的叶节点,那么相当于将一个 \(0\) 换成了两个 \(1\) ,所以设 \(i\) 个点的答案为 \(f[i]\) ,贡献就是 \(\dfrac i2\) ,这样第一问就求完了。

对于第二问:

\(g[i][j]\) 表示 \(i\) 个叶节点,树高为 \(j\) 的期望。然后我就不会了

所以我们换个状态,设 \(g[i][j]\) 表示 \(i\) 个叶子,深度大于等于 \(j\) 的概率。然后有

\[g[i][j]=\sum \dfrac{g[k][j-1]+g[i-k][j-1]-g[k][j-1]\times g[i-k][j-1]}{i-1} \]

\(k\) 是用来枚举左子树叶子个数的,然后 \(i-1\) 就要瞎证一波等概率了……搞了好久。

//Author: RingweEH
if ( typ==1 )
{
	for ( int i=2; i<=n; i++ ) g[i]=g[i-1]+2.0/(1.0*i);
	printf( "%.6lf\n",g[n] ); return 0;
}
for ( int i=1; i<=n; i++ ) f[i][0]=1;
for ( int i=2; i<=n; i++ )
	for ( int j=1; j<i; j++ )
	{
		for ( int k=1; k<i; k++ )
			f[i][j]+=f[k][j-1]+f[i-k][j-1]-f[k][j-1]*f[i-k][j-1];
		f[i][j]/=(db)(i-1);
	}
db ans=0;
for ( int i=1; i<n; i++ ) ans+=f[n][i];
printf( "%.6lf\n",ans );

P3750 分手是祝愿

给定 \(n\) 个灯的初始状态,要让所有灯熄灭。每次随机操作一个开关 \(i\) ,所有编号为 \(i\) 的约数的都取反,如果操作次数少于某个数就直接走最优方案。求操作次数期望。


想了半天突然发现想复杂了……注意到一个灯如果按了,肯定不会影响后面的灯,也就是说如果没有概率这回事,只要从后往前看到亮的就按即可。

\(f[i]\) 表示 \(i\) 次开关结束的局面转移到 \(i-1\) 次完成的期望步数。于是有

\[f[i]=\dfrac in+\dfrac{n-i}i\times(f[i+1]+f[i]+1) \]

记得和 \(k\) 比较一下。

//Author: RingweEH
for ( int i=1; i<=n; i++ )
	for ( int j=i; j<=n; j+=i )
		v[j].push_back(i);
for ( int i=n; i>=1; i-- )
	if ( a[i] )
	{
		cnt++;
		for ( int j=0; j<v[i].size(); j++ )
			a[v[i][j]]^=1;
	}

ll ans=0;
if ( cnt<=k ) ans=cnt;
else
{
	f[n]=1;
	for ( ll i=n-1; i>=1; i-- )
		f[i]=(1ll+((n-i)*power(i)%Mod)*(f[i+1]+1ll)%Mod)%Mod;
	for ( int i=cnt; i>k; i-- ) (ans+=f[i])%=Mod;
	(ans+=k)%=Mod;
}
for ( int i=1; i<=n; i++ )
	(ans*=i)%=Mod;
printf( "%lld\n",ans );

P4457 治疗之雨

\(m+1\) 个数,第一个为 \(p\) ,最小为 \(0\) 最大为 \(n\) ,剩下都是无穷。每次:

  • 在非最大值的数中随机一个加一
  • 在非最小值中随机一个减一(\(k\) 次)

\(p\to 0\) 的期望次数。


无穷有用吗……除了被当做无效操作拉低概率以外?

状态转移:对于当前为 \(x\) ,只能转移到 \(0\sim x\)\(x+1\) ,令 \(p[i]\) 表示一回合减少了 \(i\) 的概率,有

\[p[i]=\dfrac{\binom ki m^{k-i}}{(m+1)^k} \]

\(f[i]\) 为当前为 \(i\) 时减到 \(0\) 的期望次数,有

\[f[i]=\dfrac{m}{m+1}\left(1+\sum_{k=0}^{i-1}p[k]f[i-k]\right)+\dfrac{1}{m+1}\left(1+\sum_{k=0}^ip[k]f[i+1-k]\right) \]

然后发现是个下三角+对角线右边一个,优化版高斯消元即可。

//Author: RingweEH
n=read(); num=read(); m=read(); k=read();
if ( !k || (!m && k==1) ) { puts("-1"); continue; }
if ( !m )
{
	int res=0;
	while ( num>0 ) { if ( num<n ) num++; num-=k; res++; }
	printf( "%d\n",res ); continue;
}
int po=power(m+1,k); po=power(po,Mod-2); int tmp=1;
for ( int i=0; i<=n; i++ ) p[i]=0; int lim=min(n,k);
for ( int i=0; i<=lim; i++ )
{
	p[i]=1ll*tmp*power(m,k-i)%Mod*po%Mod;
	tmp=1ll*tmp*inv[i+1]%Mod*(k-i)%Mod;
}
memset( a,0,sizeof(a) ); int t1=power(m+1,Mod-2); int t2=1ll*m*t1%Mod;
for ( int i=1; i<n; i++ )
{
	a[i][i]++; a[i][n+1]++;
	for ( int j=0; j<i; j++ ) a[i][i-j]=bmod(a[i][i-j]+Mod-1ll*p[j]*t2%Mod);
	for ( int j=0; j<=i; j++ ) a[i][i+1-j]=bmod(a[i][i+1-j]+Mod-1ll*p[j]*t1%Mod);
}
a[n][n]++; a[n][n+1]++;
for ( int j=0; j<n; j++ ) a[n][n-j]=bmod(a[n][n-j]+Mod-p[j]);
Gauss(n+1);
printf( "%d\n",ans[num] );
posted @ 2021-01-31 20:14  MontesquieuE  阅读(193)  评论(1编辑  收藏  举报