概率和期望专题训练记录 2024.3
[abc300_e]Dice Product 3
很明显,概率是由其因子转移而来的,设
为什么是从二开始?因为乘1结果不变,不影响概率,所以只有5中情况,因为n很大,所以可以用记忆化搜索。
分数取模写的很抽象,形象的可以理解为输出
代码:
#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
因为转移存在环,所以无法直接正推,考虑逆推,设
即
注意,因为在转移时也要掷一次,所以在列第一个式子时要+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个数时,期望值为
考虑排序,若某个序列已经排好序,则对于每个位置i,max值为自身个数为
可以发现,有很多排序以及重复计算,因为涉及大小和求值,考虑权值线段树,每次加新值时统计小于等于它的数的个数,以及大于它的数的和,然后更新答案,加入新值,时间复杂度
注意取模,部分地方要*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
可以发现,当一个岛连接两个同色岛时一定不满足条件,所以若只有两个颜色,方案数为:
因为三种组合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
很明显,正算很乱,考虑用全部情况减去不合法情况。
如何计算总情况?即对于每个
如何计算不合法情况?首先要先明确不合法情况即两边之和小于第三边,所以可以枚举第三边长度,暴力计算,注意,在剩下的两边的划分中,设共能加k,方案不能直接加k,要加
代码:
#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,设
时间复杂度
可以对每个时间点i求和,记为
代码:
#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
本题是背包的思想,设
对于第i个人,他能进去的概率为
所以,转移方程为:
代码:
#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
- 若
且- 若
,加上前面都相同的概率 - 若
,则直接输出,因为若前面都相同,s2字典序一定比s1大 - 若相等,则不做处理
- 若
- 若
且 ,则当 时就一定满足条件,概率为 ,相等则记录,用于后面计算 - 若
且 ,则当 时就一定满足条件,概率为 ,相等则记录,用于后面计算 - 若
且 ,则当 时就一定满足条件,概率为 ,相等概率为 记录用于后面计算
代码:
#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
设
当
所以
代码:
#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)的方案数
不合法方案数
但四个角被计算了2次,多算的次数
总方案数
所以对于点(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
考虑到
设i的两颗子树u,v,先遍历u的概率为1/2,所以u的期望为
以此类推,i的期望为
代码:
#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的子树外,
- 两个点在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;
}
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 震惊!C++程序真的从main开始吗?99%的程序员都答错了
· 【硬核科普】Trae如何「偷看」你的代码?零基础破解AI编程运行原理
· 单元测试从入门到精通
· 上周热点回顾(3.3-3.9)
· winform 绘制太阳,地球,月球 运作规律