期望题乱做
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\) 堂课不申请:
- \(i-1\) 也不申请,那么期望花费是: \(t1=f[c[i-1]][c[i]]\)
- 申请,\(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\) 堂课申请:
-
\(i-1\)不申请 \(t1=k[i]\times f[c[i-1]][d[i]]+(1-k[i])\times f[c[i-1]][c[i]]\)
-
\(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}\) ,要么被左边淘汰,要么被右边淘汰,容斥得到:
(被左边淘汰+被右边淘汰+前 \(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_{y,x+1}=\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\) ,那么把这一项全部移到等式左边:
然后维护前缀和即可。时间复杂度 \(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\) 表示当前所剩下的晶体总个数,那么式子就是
//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\) 种邮票,还需要的次数。那么有
然后来考虑花费。设 \(g[i]\) 表示当前有 \(i\) 种邮票,还需要 \(g[i]\) 的钱得到 \(n\) 种。
化简即可。
这种思想在做这种概率期望题的时候应该还是很常用的。
//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\) ,非常的不可做,所以考虑换角度。由于题目要求是对边重标号,而边的经过次数是由两个端点决定的,那么可以考虑统计点的期望经过次数。而直接做是有后效性的,所以要列出方程然后高斯消元。
方程显然不难:
消元完了,对于每一条边,有
然后贪心放边权即可。
//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[0]=0,p[n]=1\) ,所以 \(p[i]=\dfrac in\) .
解决了概率就来考虑期望步数。设 \(f[i]\) 表示当前有 \(i\) 个是最终态的情况下到达最终态的期望步数。而转移出去的期望步数是转移出去的概率的倒数,于是有
高斯消元。而每个方程组只有三元,直接消元复杂度过高,手动消元即可。
//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}\) ,方程就是
然后就能得到发动概率
不知道为什么快读会出问题……
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])\) ,两部分合起来就有
//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\) 的概率,那么有
然后再高斯消元就好了。注意处理自环。
//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\) 的概率。然后有
\(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\) 次完成的期望步数。于是有
记得和 \(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\) 的概率,有
设 \(f[i]\) 为当前为 \(i\) 时减到 \(0\) 的期望次数,有
然后发现是个下三角+对角线右边一个,优化版高斯消元即可。
//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] );