概率与期望专项
\(\mathbf{Before}\) \(\mathbf{it}\):注意在做题的时候积累套路,别再看见概率期望题一点思路没有了!!
考虑到最终所得到的整数是由骰子上的数相乘所得,可以考虑分解因数。
1,2,3,4,5,6 中质因子为 2,3,5 ,所以最终的答案也一定是由 2,3,5 相乘得来。
由此可以判断无解情况,即分解出的因数中含除 2,3,5 以外的其他因子。
剩余情况考虑dp。
设 \(dp[i][j][k]\) 表示使用 \(i\) 个 \(2\) ,\(j\) 个 \(3\) ,\(k\) 个 \(5\) 相乘得到的答案。
分别对摇出2,3,4,5,6,的不同情况进行转移即可。(如以下是2的转移)
代码
#include<bits/stdc++.h>
#define int long long
using namespace std;
int n,a,b,c;
const int mod=998244353;
int dp[105][105][105];
int qpow(int a,int b){
int ans=1;
while(b){
if(b&1){
ans=ans*a%mod;
}
a=a*a%mod;
b>>=1;
}
return ans;
}
signed main(){
ios::sync_with_stdio(0);
cin.tie(0),cout.tie(0);
cin>>n;
while(n%2==0)
n/=2,a++;
while(n%3==0)
n/=3,b++;
while(n%5==0)
n/=5,c++;
if(n!=1){
cout<<0<<endl;
return 0;
}
int g=qpow(5,mod-2);//预处理5的逆元
dp[0][0][0]=1;
for(int i=0;i<=a;i++){
for(int j=0;j<=b;j++){
for(int k=0;k<=c;k++){
dp[i+1][j][k]=(dp[i+1][j][k]+dp[i][j][k]*g)%mod;
dp[i][j+1][k]=(dp[i][j+1][k]+dp[i][j][k]*g)%mod;
dp[i+2][j][k]=(dp[i+2][j][k]+dp[i][j][k]*g)%mod;
dp[i][j][k+1]=(dp[i][j][k+1]+dp[i][j][k]*g)%mod;
dp[i+1][j+1][k]=(dp[i+1][j+1][k]+dp[i][j][k]*g)%mod;
}
}
}
cout<<dp[a][b][c]<<endl;
return 0;
}
和上题相似,但数字的得到方式变成加法。
由于所求为期望,我们考虑逆推。
设 \(dp[i]\) 表示由当前 \(i\) 走到 \(n\) 的期望步数。
第 \(i\) 个格子到第 \(n\) 个格子的期望步数等于它能直接到达的格子的期望步数+1再除以 \(i\) 能到达的格子数量(即转移概率)。
即:
化简得:
最后可以得到:
考虑使用后缀和维护 \(\sum_{j=1}^{a_i} dp_{i+j}\) 的值。
代码
//逆推,感觉不像上道题那样可以分解因子啊
/*从n-1倒着往前推(因为每个格子只能向右跳)
dp[i]=它能直接到达的格子的期望步数+1,再除以i能到达的各自的数量*/
#include<bits/stdc++.h>
#define int long long
using namespace std;
const int maxn=2e5+10;
const int mod=998244353;
int n,inv[maxn],dp[maxn],a[maxn],sum[maxn];
signed main(){
ios::sync_with_stdio(0);
cin.tie(0),cout.tie(0);
cin>>n;
for(int i=1;i<n;i++){
cin>>a[i];
}
inv[1]=1;
for(int i=2;i<maxn;i++){
inv[i]=(mod-mod/i)*inv[mod%i]%mod;
}
for(int i=n-1;i>=1;i--){
int t=(sum[i+1]-sum[i+a[i]+1]+mod)%mod;
dp[i]=(a[i]+1+t)%mod*inv[a[i]]%mod;
sum[i]=(sum[i+1]+dp[i])%mod;
}
cout<<dp[1]<<endl;
}
数据结构优化题。
首先从题目中的 \(\max(a_i,a_j)\) 中入手,考虑到第 \(x\) 次询问,答案是 $ \dfrac{\sum_{i=1}^x \sum_{j=1}^x \max(a_i,a_j)}{x^2}$ 。然后分类,把 \(1\) 到 \(i-1\) 的球分为两类,一类权值小于 \(val_i\) ,个数为 \(cnt\) 个;一类权值大于 \(val_i\) ,所有数的和为 \(sum\) 。设分子为 \(c_x\) ,那么只需要计算新加入的数 \(a_x\) 对其的影响。对于第一类,\(\max(a_i,a_x)=a_x\) ,否则\(\max(a_i,a_x)=a_i\)。注意选到相同的两个数调换顺序为两种情况。
转移方程:$$c_x=c_{x-1}+cnt\times a_x\times2+sum\times2+a_x$$
代码
//分类讨论?
/*考虑到总共的情况总是n^2的,每次只需要考虑当前取出来的两个对答案有没有影响*/
//数据结构优化
/*设b[x]表示第x操作的期望权值
b[x]=x^-2*cinma max(a[i],a[j])
递推计算后半部分的值
开两个树状数组分别维护a[1]--a[x-1]中<=a[x]的个数cnt
以及 a[1]--a[x-1]中>a[x]的数的和sum
c[x]=c[x-1]+cnt*a[x]*2+sum*2+a[x]*/
#include<bits/stdc++.h>
#define lowbit(x) x&(-x)
#define int long long
using namespace std;
const int maxn=2e5+10;
const int mod=998244353;
int n,a[maxn];
int cnt[maxn],sum[maxn],inv[maxn];
void add(int x,int val){
for(int i=x;i<=maxn;i+=lowbit(i))
cnt[i]+=val;
}
void addsum(int x,int val){
for(int i=x;i<=maxn;i+=lowbit(i)){
sum[i]+=val;
sum[i]%=mod;
}
}
int query(int x){
int ans=0;
for(int i=x;i;i-=lowbit(i)){
ans+=cnt[i];
}
return ans;
}
int querysum(int x){
int ans=0;
for(int i=x;i;i-=lowbit(i)){
ans+=sum[i];
ans%=mod;
}
return ans%mod;
}
int qpow(int a,int b){
int ans=1;
while(b){
if(b&1){
ans=ans*a%mod;
}
a=a*a%mod;
b>>=1;
}
return ans;
}
signed main(){
ios::sync_with_stdio(0);
cin.tie(0),cout.tie(0);
cin>>n;
for(int i=1;i<=n;i++){
cin>>a[i];
}
int c=0;
int t=0;
for(int i=1;i<=n;i++){
c=(c+query(a[i])*a[i]%mod*2%mod)%mod;
int qwq=((t-querysum(a[i]))%mod+mod)%mod;
//我能把前缀和当单个查询来写。。。
c=(c+qwq*2%mod+mod)%mod;
c=(c+a[i])%mod;
cout<<c*qpow(i*i%mod,mod-2)%mod<<endl;
add(a[i],1);
addsum(a[i],a[i]);
t=(t+a[i])%mod;
}
}
入手点全在题面里。(收获是遇见不会做的题,再读一遍题。)
首先,由“任意两个不同的、颜色相同的小岛的最短距离要大于等于三”,可以得出“同色岛不能相连,一个岛屿不能和另外两个同色岛同时连”。
此时情况只有三种,\(ab\) 相连, \(bc\) 相连,\(ca\) 相连。
然后考虑计算答案,从 \(a\) 与 \(b\) 中各挑 \(i\) 个的方案数=从 \(a\) 中挑 \(i\) 个的方案数 \(*\) 从 \(b\) 中挑 \(i\) 个的方案数 \(*\) \(i\) 个数的排列情况总数,分别计算,最终相乘即可。
代码大概是思路的形成和实现的总和。
代码
//两个颜色相同的小岛最短距离>=3
/*得出结论,同色岛不能相连,一个岛屿不能和另外两个同色岛同时连
此时情况只有三种,a-b,b-c,a-c*/
#include<bits/stdc++.h>
#define int long long
using namespace std;
const int mod=998244353;
const int maxn=5005;
int a,b,c;
int f[maxn],g[maxn],C[maxn][maxn];
int qpow(int a,int b){
int ans=1;
while(b){
if(b&1){
ans=ans*a%mod;
}
a=a*a%mod;
b>>=1;
}
return ans;
}
int qC(int n,int m){
if(m>n)
return 0;
return f[n]*g[m]%mod*g[n-m]%mod;
}
//从a与b中各挑i个方案数 =从a中挑i个数的方案数*从b中挑i个数的方案数 *i个数的排列情况总数。
int solve(int a,int b){
int x=min(a,b),ans=1;
for(int i=1;i<=x;i++){
ans=(ans+C[a][i]*C[b][i]%mod*f[i]%mod)%mod;
}
return ans;
}
void init(){
f[0]=g[0]=1;
for(int i=1;i<maxn;i++){
f[i]=f[i-1]*i%mod;//阶乘
g[i]=g[i-1]*qpow(i,mod-2)%mod;//阶乘逆元
}
}
signed main(){
ios::sync_with_stdio(0);
cin.tie(0),cout.tie(0);
cin>>a>>b>>c;
init();
for(int i=1;i<maxn;i++){
for(int j=0;j<=i;j++){
C[i][j]=qC(i,j);
}
}
cout<<(solve(a,b)*solve(a,c)%mod*solve(b,c))%mod<<endl;
}
一点思路没有的神仙题。
首先点亮城市的顺序随机,而要计算第 \(n\) 秒的瞬间被照亮的点数的期望值,所以考虑将贡献拆开,对每个点单独计算它被选中的概率再求和即为答案。(类比线性期望可加!积累这个套路!)
其次正难则反,对于每一个点来说在第 \(n\) 秒被点亮的情况有很多,较难计算,可以考虑求在第 \(n\) 秒不会被点亮的方案数。
对于一个点是否被点亮,可以通过点亮城市的顺序确定。
设 \(dis_{i,j}\) 表示距离第 \(i\) 个点 \(j\) 个距离单位的点的个数。
第一个被点亮的城市,从这里出发的光束会走 \(n\) 个距离单位,如果不想被点亮,第一个位置只能放距离当前枚举的点长度为 \(n+1\) 的城市,同理,第二个位置可以放距离这个点单位为 \(n\) 的城市意外,还可以放第一次没有选的 \(dis_{i,n+1}-1\) 个城市。
对于每一个点,求出不能到达的方案数 \(cnt\) ,对答案的贡献即为:$$1- \dfrac {1-cnt} {n!}$$
点击查看代码
//首先进行拆点计算 ,每个点被选中的概率求和
//注意不是城市
//正难则反
#include<bits/stdc++.h>
#define int long long
using namespace std;
const int mod=998244353;
const int maxn=5e4+10;
int n,m,d[30][maxn];
int dis[maxn][30],ans;//表示距离第i个点j个单位的点的个数
int qpow(int a,int b){
int ans=1;
while(b){
if(b&1){
ans=ans*a%mod;
}
a=a*a%mod;
b>>=1;
}
return ans;
}
signed main(){
ios::sync_with_stdio(0);
cin.tie(0),cout.tie(0);
cin>>n>>m;
for(int i=1;i<=n;i++){
for(int j=1;j<=m;j++){
cin>>d[i][j];
dis[j][d[i][j]]++;
}
}
int fac=1,sum=0,nott=1;
//nott表示对于每一个点不能到达的方案
for(int i=1;i<=n;i++){
fac=fac*i%mod;
}
fac=qpow(fac,mod-2);
for(int i=1;i<=m;i++){
sum=0,nott=1;
for(int j=1;j<=n;j++){
sum+=dis[i][n+2-j];
nott=nott*sum%mod;
sum--;//减去使用过的那个
}
ans=((ans+1ll-fac%mod*nott)%mod+mod)%mod;
}
cout<<ans<<endl;
}
看起来是简单题实则很不好想。
暴力枚举显然是不现实的,正难则反,我们考虑容斥原理,使用所有组合的方案数减去不合法的方案数。
对于不合法的方案数一定满足 \(a\) , \(b\) , \(c\) 中有一个数大于另外两个数的和。分别考虑 \(a\) , \(b\) , \(c\) 做三角形最长边的情况,比如取 \(c\) 做最长边,那么枚举加的长度 \(i\) ,最多还有 $\min(c+i-a-b,l-i) $ 分配给 \(a\) 和 \(b\),乘法原理计算即可。
考虑总共的方案数,也就是把最多 \(l\) 个小球放在 \(3\) 个有标记的盒子,允许不放,考虑插板法,枚举 \(l\) ,答案也就是 \(C(l+2,2)\)。
点击查看代码
//想到了怎么判断方案不合法,但是没想到容斥原理
//对于不合法的方案分别考虑abc做三角形最长边的情况
//比如取c做最长边,枚举加的长度,那么还有最多min(c+i-a-b,l-i)的部分可以分配给ab
#include<bits/stdc++.h>
#define int long long
using namespace std;
const int maxn=3e5+10;
const int mod=998244353;
int a,b,c,l,ans;
int solve(int x,int y,int z){
int tot=0;
for(int i=0;i<=l;i++){
int tt=min(z+i-x-y,l-i);
if(tt>=0){
tot+=(tt+2)*(tt+1)/2;
}
}
return tot;
}
signed main(){
ios::sync_with_stdio(0);
cin.tie(0),cout.tie(0);
cin>>a>>b>>c>>l;
for(int i=0;i<=l;i++){
ans+=(i+2)*(i+1)/2;
}
ans-=solve(a,b,c)+solve(b,c,a)+solve(a,c,b);
cout<<ans<<endl;
}
dp。所求概率,考虑顺推。
设\(dp_i\) 表示第 \(i\) 秒歌曲刚好播完,即开启下一首歌的概率。
因为随机放下一首歌的概率是相等的,因此有:$$dp_{i+t_i}=dp_{i+t_j}+\frac{1}{n}dp_i$$。
在统计答案时,将 \(dp_{X-t_1+1}\) 到 \(dp_{X}\) 的值加起来,乘上 \(\frac{1}{n}\)即可。
其他见代码。
点击查看代码
#include<bits/stdc++.h>
#define int long long
using namespace std;
const int mod=998244353;
const int maxn=1e4+10;
int n,x,a[maxn],dp[maxn];
//设dp[i]表示i时刻正好结束播出某首歌的总概率
int qpow(int a,int b){
int ans=1;
while(b){
if(b&1){
ans=ans*a%mod;
}
a=a*a%mod;
b>>=1;
}
return ans;
}
signed main(){
ios::sync_with_stdio(0);
cin.tie(0),cout.tie(0);
cin>>n>>x;
for(int i=1;i<=n;i++){
cin>>a[i];
}
dp[0]=1;
int t=qpow(n,mod-2);
for(int i=0;i<=x;i++){
for(int j=1;j<=n;j++){
if(i>=a[j]){
dp[i]=(dp[i]+dp[i-a[j]]*t)%mod;
}
}
}
int qwq=0;
for(int i=max(0ll,x-a[1]+1);i<=x;i++){
qwq+=dp[i];
qwq%=mod;
}
cout<<qwq*t%mod<<endl;
}