概率和期望专题训练记录 2024.3

[abc300_e]Dice Product 3

很明显,概率是由其因子转移而来的,设\(dp[i]\)表示结果为i的概率,则有转移方程:

\[dp_i=\sum_{j=2}^6 dp_{\frac{i}{j}}\times \frac{1}{5}\times[i \bmod j =0] \]

为什么是从二开始?因为乘1结果不变,不影响概率,所以只有5中情况,因为n很大,所以可以用记忆化搜索。

分数取模写的很抽象,形象的可以理解为输出\(P\times Q^{-1} \ (\bmod\ 998244353)\)

代码:

#include<cstdio>
#include<map>
#define ll long long
using namespace std;
const int m=998244353;
ll n,d;
map<ll,ll> mp;
ll qpow(ll x,int y)
{
	ll res=1;
	while(y)
	{
		if(y&1) res=res*x%m;
		x=x*x%m;
		y>>=1;
	}
	return res;
}
void dfs(ll x)
{
	if(mp[x]) return;
	for(int i=2;i<=6;i++)
	{
		if(x%i!=0) continue;
		dfs(x/i);
		mp[x]=(d*mp[x/i]%m+mp[x])%m;
	}
}
int main()
{
	scanf("%lld",&n);
	d=qpow(5,m-2);
	mp[1]=1;
	dfs(n);
	printf("%lld",mp[n]);
	return 0;
}

[abc263_e]Sugoroku 3

因为转移存在环,所以无法直接正推,考虑逆推,设\(dp[i]\)表示当前在i,期望还需走的步数,易得转移方程:

\[dp_i=\frac{\sum_{j=i}^{i+a_i} (dp_j+1)}{a_i+1} \]

\[dp_i=\frac{\sum_{j=i+1}^{i+a_i}dp_j}{a_i}+\frac{a_i+1}{a_i} \]

注意,因为在转移时也要掷一次,所以在列第一个式子时要+1,不然会推出某些奇怪的东西

代码:

#include<cstdio>
#define ll long long
using namespace std;
const int m=998244353;
int n,a[200005];
ll dp[200005],sum[400005];
ll qpow(ll x,int y)
{
	ll res=1;
	while(y)
	{
		if(y&1) res=res*x%m;
		x=x*x%m;
		y>>=1;
	}
	return res;
}
int main()
{
	scanf("%d",&n);
	for(int i=1;i<n;i++)
	{
		scanf("%d",&a[i]);
	}
	for(int i=n-1;i>0;i--)
	{
		dp[i]=((sum[i+1]-sum[i+a[i]+1])%m*qpow(a[i],m-2)%m+(a[i]+1)*qpow(a[i],m-2)%m)%m;
		sum[i]=(sum[i+1]+dp[i])%m;
		//printf("%d ",dp[i]);
	}
	printf("%lld ",dp[1]);
}

[abc276_f]Double Chance

当有k个数时,期望值为\(\frac{\sum_{i=1}^k\sum_{j=1}^k \max(a_i,a_j)}{k^2}\),时间复杂度\(O(n^3)\)

考虑排序,若某个序列已经排好序,则对于每个位置i,max值为自身个数为\(2\times i-1\),所以可以排序后\(O(n)\)求解,时间复杂度\(O(n^2\log n)\)

可以发现,有很多排序以及重复计算,因为涉及大小和求值,考虑权值线段树,每次加新值时统计小于等于它的数的个数,以及大于它的数的和,然后更新答案,加入新值,时间复杂度\(O(n \log n)\)

注意取模,部分地方要*1ll

代码:

#include<cstdio>
#define lid id<<1
#define rid id<<1|1
#define ll long long
using namespace std;
const int p=998244353;
int n,a[200005],m=2e5;
ll qpow(ll x,int y)
{
	ll res=1;
	while(y)
	{
		if(y&1) res=res*x%p;
		x=x*x%p;
		y>>=1;
	}
	return res;
}
struct seg_tree{
	int l,r;
	ll sum,cnt;
}tr[800040];
void build(int id,int l,int r)
{
	tr[id].l=l,tr[id].r=r;
	if(l==r)
	{
		return;
	}
	int mid=(l+r)>>1;
	build(lid,l,mid);
	build(rid,mid+1,r);
}
void update(int id,int x)
{
	if(tr[id].l==tr[id].r)
	{
		tr[id].sum=(tr[id].sum+1ll*x)%p;
		tr[id].cnt=(tr[id].cnt+1)%p;
		return;
	}
	int mid=(tr[id].l+tr[id].r)>>1;
	if(x<=mid) update(lid,x);
	else update(rid,x);
	tr[id].sum=(tr[lid].sum+tr[rid].sum)%p;
	tr[id].cnt=(tr[lid].cnt+tr[rid].cnt)%p;
}
ll query1(int id,int l,int r)
{
	if(tr[id].l==l&&tr[id].r==r)
	{
		return tr[id].sum;
	}
	int mid=(tr[id].l+tr[id].r)>>1;
	if(r<=mid) return query1(lid,l,r)%p;
	else if(l>mid) return query1(rid,l,r)%p;
	else return (query1(lid,l,mid)+query1(rid,mid+1,r))%p;
}
ll query2(int id,int l,int r)
{
	if(tr[id].l==l&&tr[id].r==r)
	{
		return tr[id].cnt;
	}
	int mid=(tr[id].l+tr[id].r)>>1;
	if(r<=mid) return query2(lid,l,r)%p;
	else if(l>mid) return query2(rid,l,r)%p;
	else return (query2(lid,l,mid)+query2(rid,mid+1,r))%p;
}
int main()
{
	scanf("%d",&n);
	build(1,1,m);
	for(int i=1;i<=n;i++)
	{
		scanf("%d",&a[i]);
	}
	ll ans=0;
	for(int i=1;i<=n;i++)
	{
		ll x=query2(1,1,a[i])*2%p+1,y=query1(1,a[i]+1,m)*2%p,k=1ll*i*i%p,q=1ll*(i-1)*(i-1)%p;
		ans=(ans*q%p+x*a[i]%p+y)%p*qpow(k,p-2)%p;
		update(1,a[i]);
		printf("%lld\n",ans);
	}
	return 0;
}

[cf869C] The Intriguing Obsession

可以发现,当一个岛连接两个同色岛时一定不满足条件,所以若只有两个颜色,方案数为:

\[\sum_{i=0}^{min(a,b)} C_{a}^{i}\times C_{b}^{i}\times i! \]

因为三种组合AB,BC,AC互不干扰,所以可以分别按上述公式求解,最后求乘积。

代码:

#include<cstdio>
#include<algorithm>
#define ll long long
using namespace std;
const int p=998244353;
int a,b,c;
ll fac[5005],inv[5005];
ll qpow(ll x,int y)
{
	ll res=1;
	while(y)
	{
		if(y&1) res=res*x%p;
		x=x*x%p;
		y>>=1;
	}
	return res;
}
ll C(int m,int n)
{
	return fac[n]*inv[n-m]%p*inv[m]%p;
}
ll solve(int x,int y)
{
	ll res=1;
	for(int i=1;i<=min(x,y);i++)
	{
		res+=C(i,x)*C(i,y)%p*fac[i]%p;
		res%=p;
	}
	return res;
}
int main()
{
	scanf("%d%d%d",&a,&b,&c);
	fac[0]=inv[0]=1;
	for(int i=1;i<=5000;i++)
	{
		fac[i]=fac[i-1]*i%p;
	}
	inv[5000]=qpow(fac[5000],p-2);
	for(int i=4999;i>0;i--)
	{
		inv[i]=inv[i+1]*(i+1)%p;
	}
//	printf("%d ",inv[1]);
	printf("%lld",solve(a,b)*solve(b,c)%p*solve(c,a)%p);
	return 0;
}

[cf1525E]Assimilation IV

依据题面,可以知道每个点只会被计算一次,所以可以从点出发,求每个点被覆盖的概率,正着计算会有很多重复,所以考虑先算出不可能的情况,在与1作差,很明显,若所有城市到点A的距离都小于n,则一定成立,如果有一个不满足,则若此城市第一个放置,就要分两种情况,若其余距离均小于n-1,则成立,否则以此类推即可求得答案

代码:

#include<cstdio>
#define ll long long
using namespace std;
const int p=998244353;
int n,m;
int t[50005][25];
ll fac[25],ans;
ll qpow(ll x,int y)
{
	ll res=1;
	while(y)
	{
		if(y&1) res=res*x%p;
		x=x*x%p;
		y>>=1;
	}
	return res;
}
int main()
{
	scanf("%d%d",&n,&m);
	fac[0]=1;
	for(int i=1;i<=n;i++)
	{
		fac[i]=fac[i-1]*i%p;
	}
	for(int i=1;i<=n;i++)
	{
		for(int j=1;j<=m;j++)
		{
			int d;
			scanf("%d",&d);
			t[j][d]++;
		}
	}
	ll inv=qpow(fac[n],p-2);
	for(int i=1;i<=m;i++)
	{
		ll sum=0,tmp=1;
		for(int j=n;j>0;j--)
		{
			sum+=t[i][j+1];
			tmp=tmp*sum%p;
			sum--;
		}
		ans=(ans+1-tmp*inv%p+p)%p;
	}
	printf("%lld",ans);
	return 0;
}

[cf571A]Lengthening Sticks

很明显,正算很乱,考虑用全部情况减去不合法情况。

如何计算总情况?即对于每个\(0\le i\le l\),将i划分成3份,方案数即:

\[1+\sum_{i=1}^{l}C_{i+2}^2 \]

如何计算不合法情况?首先要先明确不合法情况即两边之和小于第三边,所以可以枚举第三边长度,暴力计算,注意,在剩下的两边的划分中,设共能加k,方案不能直接加k,要加\(\sum_{i=0}^{k}A_{i+1}^l=\sum_{i=0}^{k}i+1=(i+1)\times(i+2)\div2\)

代码:

#include<cstdio>
#include<algorithm>
#define ll long long
using namespace std;
int a,b,c,l;
ll ans;
ll solve(int x,int y,int z)
{
	ll res=0;
	for(int i=max(0,x+y-z);i<=l;i++)
	{
		int tmp=min(l-i,z+i-x-y);
		res=res+1ll*(tmp+1)*(tmp+2)/2;
	}
	return res;
}
int main()
{
	scanf("%d%d%d%d",&a,&b,&c,&l);
	ans=1;
	for(int i=1;i<=l;i++)
	{
		ans=ans+1ll*(i+1)*(i+2)/2;
	}
//	printf("%lld\n",ans);
	ans-=solve(a,b,c);
	ans-=solve(b,c,a);
	ans-=solve(c,a,b);
	printf("%lld",ans);
	return 0;
}

ABC323E Playlist

考虑dp,设\(dp_{i,j}\)表示在时刻i,j恰好放完的概率,易得转移方程:

\[dp_{i,j}=\sum_{k=1}^n dp_{i-t_j,k} \]

时间复杂度\(O(n^2x)\),考虑优化

可以对每个时间点i求和,记为\(sum_i\),则转移方程为:

\[dp_{i,j}=sum_{i-t_j} \]

代码:

#include<cstdio>
#define ll long long
using namespace std;
const int p=998244353;
ll inv;
ll qpow(ll x,int y)
{
	ll res=1;
	while(y)
	{
		if(y&1) res=res*x%p;
		x=x*x%p;
		y>>=1;
	}
	return res;
}
int n,t[1005],x;
ll dp[20005][1003],sum[20005];
int main()
{
	scanf("%d%d",&n,&x);
	for(int i=1;i<=n;i++)
	{
		scanf("%d",&t[i]);
	}
	inv=qpow(n,p-2);
	if(x==0)
	{
		printf("%lld",inv);
		return 0;
	}
	sum[0]=1;
	for(int i=1;i<=t[1]+x;i++)
	{
		for(int j=1;j<=n;j++)
		{
			if(i>=t[j])
			{
				dp[i][j]=sum[i-t[j]]*inv%p;
			}
			sum[i]=(dp[i][j]+sum[i])%p;
		}
	}
	ll ans=0;
	for(int i=x+1;i<=t[1]+x;i++)
	{
		ans=(dp[i][1]+ans)%p;
	}
	printf("%lld",ans);
	return 0;
}

[cf261B]Maxim and Restaurant

本题是背包的思想,设\(dp_{i,j,k}\)表示前i个人中,进去j个人,占地为k的期望。

对于第i个人,他能进去的概率为\(C_{i-1}^{j-1}\div C_i^j=\frac{j}{i}\)

所以,转移方程为:

\[dp_{i,j,k}=\frac{j}{i}\times dp_{i-1,j-1,k-a_i}+\frac{i-j}{i}\times dp_{i-1,j,k} \]

代码:

#include<cstdio>
using namespace std;
int n,a[55],p;
double f[55][55][55];
int main()
{
	scanf("%d",&n);
	for(int i=1;i<=n;i++)
	{
		scanf("%d",&a[i]);
	}
	scanf("%d",&p);
	f[0][0][0]=1;
	for(int i=1;i<=n;i++)
	{
		for(int j=0;j<=i;j++)
		{
			for(int k=0;k<=p;k++)
			{
				f[i][j][k]=f[i-1][j][k]*(i-j)/(i*1.0);
				if(k>=a[i])
				f[i][j][k]+=f[i-1][j-1][k-a[i]]*j/(i*1.0);
			}
		}
	}
	double ans=0;
	for(int j=1;j<=n;j++)
	{
		for(int k=1;k<=p;k++)
		{
			ans+=f[n][j][k];
		}
	}
	printf("%.10lf",ans);
	return 0;
}

[cf935D]Fafa and Ancient Alphabet

讲一个暴力的方法,分四种情况考虑,设当前位置为i

  • \(s1_i\neq 0\)\(s2_i\neq 0\)
    • \(s1_i>s2_i\),加上前面都相同的概率
    • \(s1_i<s2_i\),则直接输出,因为若前面都相同,s2字典序一定比s1大
    • 若相等,则不做处理
  • \(s1_i\neq 0\)\(s2_i= 0\),则当\(s2_i<s1_i\)时就一定满足条件,概率为\(\frac{s1_i-1}{m}\),相等则记录,用于后面计算
  • \(s1_i= 0\)\(s2_i\neq 0\),则当\(s2_i<s1_i\)时就一定满足条件,概率为\(\frac{m-s2_i}{m}\),相等则记录,用于后面计算
  • \(s1_i= 0\)\(s2_i= 0\),则当\(s2_i<s1_i\)时就一定满足条件,概率为\(\frac{m(m-1)}{2m^2}\),相等概率为\(\frac{1}{m}\)记录用于后面计算

代码:

#include<cstdio>
#define ll long long
using namespace std;
const int p=1e9+7;
int n,m,a[100005],b[100005];
ll ans;
ll qpow(ll x,int y)
{
	ll res=1;
	while(y)
	{
		if(y&1) res=res*x%p;
		x=x*x%p;
		y>>=1;
	}
	return res;
}
int main()
{
	scanf("%d%d",&n,&m);
	for(int i=1;i<=n;i++)
	{
		scanf("%d",&a[i]);
	}
	for(int i=1;i<=n;i++)
	{
		scanf("%d",&b[i]);
	}
	ll tmp=1,inv=qpow(m,p-2);
	for(int i=1;i<=n;i++)
	{
		if(a[i]!=0&&b[i]!=0)
		{
			if(a[i]>b[i])ans+=tmp;
			if(a[i]!=b[i]) break;
		}
		if(a[i]==0&&b[i]!=0)
		{
			ans=(ans+tmp*(m-b[i])%p*inv%p)%p;
			tmp=tmp*inv%p;
		}
		if(a[i]!=0&&b[i]==0)
		{
			ans=(ans+tmp*(a[i]-1)%p*inv%p)%p;
			tmp=tmp*inv%p;
		}
		if(a[i]==0&&b[i]==0)
		{
			ans=(ans+(1ll*m*(m-1)/2)%p*tmp%p*inv%p*inv%p)%p;
			tmp=tmp*inv%p;
		}
	}
	printf("%lld",ans%p);
	return 0;
}

[cf1777D]Score of a Tree

\(d_i\)表示i到其子树深度最大的节点的距离+1,若\(t>d_i\),则i的值为0

\(t<d_i\)时,i的值为与i距离为t的子节点的异或和, 此时节点i的值有1/2的概率为1

所以\(ans=2^{n-1}\sum d_i\)

代码:

#include<cstdio>
#include<algorithm>
#define ll long long
using namespace std;
const int p=1e9+7;
int n,T,head[200005],edgenum;
struct edge{
	int to,nxt;
}e[400005];
void add(int u,int v)
{
	e[++edgenum].nxt=head[u];
	e[edgenum].to=v;
	head[u]=edgenum;
}
int d[200005];
void dfs(int u,int fa)
{
	d[u]=1;
	for(int i=head[u];i;i=e[i].nxt)
	{
		int v=e[i].to;
		if(fa==v) continue;
		dfs(v,u);
		d[u]=max(d[v]+1,d[u]);
	}
}
int main()
{
	scanf("%d",&T);
	while(T--)
	{
		scanf("%d",&n);
		for(int i=1;i<n;i++)
		{
			int u,v;
			scanf("%d%d",&u,&v);
			add(u,v);
			add(v,u);
		}
		dfs(1,0);
		ll ans=0,tmp=1;
		edgenum=0;
		for(int i=1;i<=n;i++)
		{
			head[i]=0;
			ans=(ans+d[i])%p;
			if(i!=1) tmp=tmp*2%p;
		}
		ans=ans*tmp%p;
		printf("%lld\n",ans);
	}
	return 0;
}

[abc297_f]Minimum Bounding Box 2

考虑每个点对整体的贡献,即总方案数减去不符合的方案数,不符合的方案数即不包含(i,j)的方案数

不合法方案数\(a=C_{(i-1)*m}^k+C_{(n-i)*m}^k+C_{(j-1)*n}^k+C_{(m-j)*n}^k\)

但四个角被计算了2次,多算的次数\(b=C_{(i-1)*(j-1)}^k+C_{(i-1)*(m-j)}^k+C_{(j-1)*(n-i)}^k+C_{(n-i)*(m-j)}^k\)

总方案数\(c=C_{n*m}^k\)

所以对于点(i,j),方案数就是c-a+b

代码:

#include<cstdio>
#include<algorithm>
#define ll long long
using namespace std;
const int p=998244353;
int n,m,k;
ll fac[1000006],inv[1000006];
ll qpow(ll x,int y)
{
	ll res=1;
	while(y)
	{
		if(y&1) res=res*x%p;
		x=x*x%p;
		y>>=1;
	}
	return res;
}
ll C(int n,int m)
{
	if(m>n) return 0;
	return fac[n]*inv[m]%p*inv[n-m]%p;
}
int main()
{
	scanf("%d%d%d",&n,&m,&k);
	fac[0]=1;
	for(int i=1;i<=m*n;i++)
	{
		fac[i]=fac[i-1]*i%p;
	}
	inv[n*m]=qpow(fac[n*m],p-2);
	for(int i=n*m-1;i>=0;i--)
	{
		inv[i]=inv[i+1]*(i+1)%p;
	}
	ll ans=0;
	for(int i=1;i<=n;i++)
	{
		for(int j=1;j<=m;j++)
		{
			ll a=(C((i-1)*m,k)+C((n-i)*m,k)+C((j-1)*n,k)+C((m-j)*n,k))%p;
			ll b=(C((i-1)*(j-1),k)+C((i-1)*(m-j),k)+C((j-1)*(n-i),k)+C((n-i)*(m-j),k))%p;
			ll c=C(n*m,k);
			ans=(ans+c-a+b+p)%p;
		}
	}
	printf("%lld",ans*qpow(C(n*m,k),p-2)%p);
	return 0;
}

[cf696B]Puzzles

考虑到\(starting\_time[i]>starting\_time[fa_i]\)且将一颗子树遍历完后才会遍历其他子树,所以i的转移一定和它的父亲有关

设i的两颗子树u,v,先遍历u的概率为1/2,所以u的期望为\(\frac{size_v+1}{2}\)

以此类推,i的期望为\(dp_{fa_i}+1+\frac{size_{fa_i}-size_{u}-1}{2}\)

代码:

#include<cstdio>
using namespace std;
int n,a[100005],head[100005],edgenum,size[100005];
struct edge{
	int to,nxt;
}e[200005];
void add(int u,int v)
{
	e[++edgenum].nxt=head[u];
	e[edgenum].to=v;
	head[u]=edgenum;
}
double dp[100005];
void dfs(int u,int fa)
{
	size[u]=1;
	for(int i=head[u];i;i=e[i].nxt)
	{
		int v=e[i].to;
		if(v==fa) continue;
		dfs(v,u);
		size[u]+=size[v];
	}
}
void dfs2(int u,int fa)
{
	if(u!=1)
	dp[u]=dp[fa]+1+(size[fa]-size[u]-1)/2.0;
	for(int i=head[u];i;i=e[i].nxt)
	{
		int v=e[i].to;
		if(v==fa) continue;
		dfs2(v,u);
	}
}
int main()
{
	scanf("%d",&n);
	for(int i=2;i<=n;i++)
	{
		int v;
		scanf("%d",&v);
		add(i,v);
		add(v,i);
	}
	dfs(1,0);
	dp[1]=1;
	dfs2(1,0);
	for(int i=1;i<=n;i++)
	{
		printf("%.1lf ",dp[i]);
	}
	return 0;
}

[cf500D]New Year Santa Network

考虑记录每一条边的经过次数

设两个节点中深度较大的点为x,则只有两种情况会经过:

  • 两个点在x的子树外,\(size_x\times(size_x-1)\times(n-size_x)\)
  • 两个点在x的子树内,\(size_x\times(n-size_x-1)\times(n-size_x)\)

代码:

#include<cstdio>
#include<algorithm>
using namespace std;
int n,head[100005],edgenum,siz[100005],dep[100005];
struct edge{
	int to,nxt,val;
}e[200005];
double sum[100005];
void add(int u,int v,int w)
{
	e[++edgenum].nxt=head[u];
	e[edgenum].to=v;
	e[edgenum].val=w;
	head[u]=edgenum;
}
void dfs(int u,int fa)
{
	siz[u]=1;
	dep[u]=dep[fa]+1;
	for(int i=head[u];i;i=e[i].nxt)
	{
		int v=e[i].to;
		if(v==fa) continue;
		dfs(v,u);
		siz[u]+=siz[v];
	}
}
int u[100005],v[100005],w[100005];
int main()
{
	scanf("%d",&n);
	for(int i=1;i<n;i++)
	{
		scanf("%d%d%d",&u[i],&v[i],&w[i]);
		add(u[i],v[i],w[i]);
		add(v[i],u[i],w[i]);
	}
	dfs(1,0);
	for(int i=1;i<n;i++)
	{
		if(dep[u[i]]>dep[v[i]]) swap(u[i],v[i]);
		sum[i]=1.0*siz[v[i]]*(n-siz[v[i]])*(siz[v[i]]-1)/2;
		sum[i]+=1.0*siz[v[i]]*(n-siz[v[i]])*(n-siz[v[i]]-1)/2;
	}
	double ans=0,s=0,tmp=1.0*n*(n-1)*(n-2);
	for(int i=1;i<n;i++)
	{
		ans+=sum[i]*w[i];
	}
	int q=0;
	scanf("%d",&q);
	while(q--)
	{
		int r,val;
		scanf("%d%d",&r,&val);
		ans+=sum[r]*(val-w[r]);
		w[r]=val;
		printf("%.10lf\n",ans*12.0/tmp);
	}
	return 0;
}
posted @ 2024-03-03 21:41  wangsiqi2010916  阅读(21)  评论(0编辑  收藏  举报