[kuangbin带你飞]专题二十一 概率&期望
A.dp不行就推公式
在n个门前选择一扇门出去,
然后如果第i扇门的 Xi值是正的话,你会花费Xi时间后出去 ,
如果Xi是负数的话你会花费-Xi时间后回到老地方,并且忘记了刚才的选择, 选择一扇门的概率是等概的。
求出去的期望。
设出去的期望是Y,
那么可以写出一个式子 :Y = P1 * T1 + P2 * (T2 + Y),
这样的话问题就得到了解决, 最后整理下式子就是 :
Y = 正数个数的倒数 * ∑abs(Xi) ;
#include<bits/stdc++.h>
using namespace std;
const int maxn=1e4+5;
const int eps=1e-5;
typedef long long ll;
typedef pair<ll,ll>p;
ll n,a[maxn];
int main()
{
ll t,cas=0;
cin>>t;
while(t--)
{
ll sum=0,cnt=0;
cin>>n;
for(ll i=1; i<=n; i++)
{
scanf("%lld",&a[i]);
sum+=abs(a[i]);
if(a[i]<0)cnt++;
}
if(cnt==n)
{
printf("Case %lld: inf\n",++cas);
}
else
{
ll gcd=__gcd(sum,n-cnt);
printf("Case %lld: %lld/%lld\n",++cas,sum/gcd,(n-cnt)/gcd);
}
}
}
B.注意边界从后转移
有一排洞穴,你在第一个洞穴,可以获得该洞穴的黄金,然后掷标有1-6的骰子,
是几就往下走几步,并得到该洞穴的黄金。
当离终点小于6步且不合法时就重掷直到合法为止。
求起点出发的黄金的期望。
直接dp向后转移
dp[i]=sigma{
dp[i+k]*p0+a[i+k]}
#include<bits/stdc++.h>
using namespace std;
const int maxn=1e2+5;
const int eps=1e-5;
typedef long long ll;
typedef pair<ll,ll>p;
int n,a[maxn];
double dp[maxn];
int main()
{
int t,cas=0;
cin>>t;
while(t--)
{
cin>>n;
for(int i=1; i<=n; i++)
scanf("%d",&a[i]);
memset(dp,0,sizeof dp);
dp[n]=0;
for(int i=n-1; i>=0; i--)
{
if(i+6>n)
{
int down=n-i;
for(int j=i+1; j<=n; j++)
{
dp[i]+=(dp[j]+a[j]);
}
dp[i]=dp[i]/down;
}
else
{
for(int j=i+1; j<=i+6; j++)
{
dp[i]+=(dp[j]+a[j]);
}
dp[i]/=6;
}
}
printf("Case %d: %.7f\n",++cas,dp[1]+a[1]);
}
}
C.很经典的从后向前转移,注意double
给定一个整数n,每次操作可以对当前数进行除以它的某个因子。
除以哪个因子是随机的,
求把n变成1的期望步数。
预处理答案:
#include<bits/stdc++.h>
using namespace std;
const int maxn=1e5+5;
const int eps=1e-5;
typedef long long ll;
typedef pair<ll,ll>p;
int n,a[maxn];
double dp[maxn];
int cal(int n)
{
int cnt=0;
for(int i=2; i*i<=n; i++)
{
if(n%i==0)
{
if(i*i!=n)
cnt+=2;
else
cnt+=1;
}
}
cnt+=2;
return cnt;
}
void init()
{
//dp[2]=2;
for(int i=2; i<=maxn-1; i++)
{
int cnt=cal(i),n=i;
for(int j=2; j*j<=n; j++)
{
if(n%j==0)
{
dp[i]+=dp[j]/(cnt-1);
if(j*j!=n)
dp[i]+=dp[n/j]/(cnt-1);
}
}
dp[i]+=(cnt)*1.0/(cnt-1);
}
}
int main()
{
init();
int t,cas=0;
cin>>t;
while(t--)
{
cin>>n;
printf("Case %d: %.7f\n",++cas,dp[n]);
}
}
E.标程答案肯定出锅,我用java写的大数跟AC代码有误差
应该是精度问题。
在某个星球上一年有 n 天,现在要邀请人参加聚会,
现在使得聚会上至少两个人同一天生日的概率最小为 0.5,
问最少有多少人?
我们用容斥原理,先求所有人生日都不同的概率,用1减去它即可。
m个人每人选一个当做生日:A(m,n)
m个人总共可能有的生日的数:n^m
那么题目就是求:1 - A(m,n)/ n^m >= 0.5
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int maxn=1e4+5;
const double eps=1e-5;
int main()
{
int t,cas=0,n;
cin>>t;
while(t--)
{
cin>>n;
int up=1,down=1,st=n,ans=0;double ok=1;
for(int i=1; i<=400; i++)
{
ok=ok*st*1.0/n;
st--;
if(ok<=0.5)
{
ans=i;
break;
}
}
printf("Case %d: %d\n",++cas,ans-1);
}
}
E.很经典的背包概率dp
有少于100个银行,每个银行有少于100元的钱,和被抓住的概率,要求你背抓住的概率小于等于v,你最多能获得多少钱.
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int maxn=1e4+5;
const double eps=1e-5;
struct node
{
double p;
int v;
} a[maxn];
double p;
int n;
double dp[maxn];
int main()
{
int t,cas=1;
cin>>t;
while(t--)
{
memset(dp,0,sizeof dp);
scanf("%lf%d",&p,&n);
for(int i=1; i<=n; i++)
scanf("%d%lf",&a[i].v,&a[i].p);
dp[0]=1;
for(int i=1;i<=n;i++)
{
for(int j=maxn;j>=a[i].v;j--)
{
dp[j]=max(dp[j-a[i].v]*(1-a[i].p),dp[j]);
}
}
int ans=0;
for(int i=0;i<maxn;i++)
{
if(dp[i]>=1-p)
ans=i;
}
printf("Case %d: %d\n",cas++,ans);
}
}
F.经典高斯消元可前可后解方程
有100个格子,从1开始走,每次抛骰子走1~6,若抛出的点数导致走出了100以外,则重新抛一次。有n个格子会单向传送到其他格子,tp[i]表示从i传送到tp[i]。
1和100不会有传送,一个格子也不会有两种传送。问走到100的期望值。
dp[i]表示从格子i走出去的期望次数
分两种情况考虑
格子不可以传送 dp[i]=6⋅∑kj=dp[i+j]+6⋅∑6j=k+1dp[i]+1(i+k=100)
格子可以传送 dp[i]=dp[nxt[i]]
化简得
格子不可以传送 k⋅dp[i]−∑kj=1dp[i+j]=6
格子可以传送 dp[i]=dp[nxt[i]]
由于传送是无序的,所以无法使用递推来解决,只能列矩阵方程来高斯消元
#include<bits/stdc++.h>
using namespace std;
const int maxn=110;
const double eps=1e-10;
double dp[maxn];
int nxt[maxn];
double a[maxn][maxn],x[maxn];//方程的左边的矩阵和等式右边的值,求解之后x存的就是结果
int equ,var;//方程数和未知数个数
int Gauss()
{
int i,j,k,col,max_r;
for(k=1,col=1; k<=equ&&col<=var; k++,col++)
{
max_r=k;
for(i=k+1; i<=equ; i++)
if(fabs(a[i][col])>fabs(a[max_r][col]))
max_r=i;
if(fabs(a[max_r][col])<eps)
return 0;
if(k!=max_r)
{
for(j=col; j<=var; j++)
swap(a[k][j],a[max_r][j]);
swap(x[k],x[max_r]);
}
x[k]/=a[k][col];
for(j=col+1; j<=var; j++)
a[k][j]/=a[k][col];
a[k][col]=1;
for(i=0; i<=equ; i++)
if(i!=k)
{
x[i]-=x[k]*a[i][k];
for(j=col+1; j<=var; j++)
a[i][j]-=a[k][j]*a[i][col];
a[i][col]=0;
}
}
return 1;
}
int main()
{
int t,cas=0;
cin>>t;
while(t--)
{
memset(a,0,sizeof a);
memset(x,0,sizeof x);
memset(dp,0,sizeof dp);
for(int i=1; i<maxn; i++)
nxt[i]=i;
equ=var=100;
int m;
cin>>m;
for(int i=1; i<=m; i++)
{
int u,v;
scanf("%d%d",&u,&v);
nxt[u]=v;
}
a[100][100]=1;
for(int i=99; i>=1; i--)
{
int cnt=min(100-i,6);
if(nxt[i]!=i)
{
a[i][i]=1;
a[i][nxt[i]]=-1;
continue;
}
a[i][i]=cnt*1.0/6,x[i]=1;
for(int j=1; j<=cnt; j++)
{
a[i][i+j]=-1.0/6;
}
}
Gauss();
printf("Case %d: %.10f\n",++cas,x[1]);
}
}
G.几何分布 E=1/p;
#include<bits/stdc++.h>
#include<tr1/unordered_map>
const int maxn=1e6+5;
const int mod=1e9+7;
const int INF=0x3f3f3f3f;
const double eps=1e-7;
typedef long long ll;
using namespace std;
int t,n,cse=0;
int main()
{
cin>>t;
while(t--)
{
scanf("%d",&n);
double ans=0;
for(int i=1; i<=n; i++)
ans=ans+n*1.0/i;
printf("Case %d: %.10f\n",++cse,ans);
}
}
H.组合数+DP
大概题意是:岛内有n只老虎和m只鹿,
每天都有两只动物呆在一起,
问最后跑出岛的概率是多少。
(1)你和老虎,老虎吃掉你
(2)老虎和鹿,老虎吃掉鹿
(3)两只鹿,两只都是安全
(4)你和鹿呆,你可以选择杀或者不杀鹿
(5)两只老虎,两只都会死掉
double dp[maxn][maxn]; // i老虎,j只鹿逃跑的概率
dp[i][j]:
人和鹿呆在一起,发生的概率为
C(1,j) / C(2,1 + i + j) * (dp[i][j] + dp[i][j - 1]) / 2
两只鹿呆在一起,发生的概率为
C(2,J) / C(2,1 + i + j) * dp[i][j]
虎和鹿呆在一起,发生的概率为
i * j / C(2,i + j + 1) * dp[i][j - 1] 鹿去掉 1
两只虎呆在一起,发生的概率为
C(2,i) / C(2,1 + i + j) * dp[i - 2][j] 老虎去掉 2
#include<bits/stdc++.h>
#include<tr1/unordered_map>
const int maxn=1e3+5;
const int mod=1e9+7;
const int INF=0x3f3f3f3f;
const double eps=1e-7;
typedef long long ll;
using namespace std;
double dp[maxn][maxn];
int T,t,d,cas=0,ok;
int c2(int n)
{
if(n==0||n==1)
return 0;
return n*(n-1)/2;
}
int main()
{
cin>>T;
while(T--)
{
scanf("%d%d",&t,&d);
memset(dp,0,sizeof dp);
for(int i=0; i<=d; i++)
dp[0][i]=1;
for(int i=0; i<=t; i++)
{
for(int j=0; j<=d; j++)
{
if(i==0)
continue;
double p=c2(i+j+1);
if(j>=1)
dp[i][j]+=dp[i][j-1]*i*j*1.0/p;
if(j>=1)
dp[i][j]+=dp[i][j-1]*j*0.5/p;
if(i>=2)
dp[i][j]+=dp[i-2][j]*c2(i)*1.0/p;
double down=1;
down-=c2(j)*1.0/p;
down-=j*0.5/p;
dp[i][j]/=down;
}
}
printf("Case %d: %.10f\n",++cas,dp[t][d]);
}
}
I.滚动数组01背包dp
期望dp,用逆推。dp[i][j][k]表示当前状态为第i行,已经输出了j个YES,这一行要输出YES(0)/NO(1)
因此可以得出,
①如果第i要输出YES,那么i+1行如果输出NO,
则i的期望值为i+1的期望值加上+1再乘以输出NO的概率;
i+1行如果输出YES,
则i的期望值为i+1的期望值乘以输出YES的概率。
②如果第i行要输出NO,做法和①相同
递推关系式为
dp[i][j][0]=dp[i+1][j+1][0]*p1+(dp[i+1][j][1]+1)*p2
dp[i][j][1]=(dp[i+1][j+1][0]+1*p1)*p1+dp[i+1][j][1]*p2
因为不能开5000*5000*2的数组,想到第i位只与第i+1位有关,第一维可以滚动。
#include<bits/stdc++.h>
#include<tr1/unordered_map>
const int maxn=5e3+5;
const int mod=1e9+7;
const int INF=0x3f3f3f3f;
const double eps=1e-7;
typedef long long ll;
using namespace std;
double dp[2][maxn][2];
int n,s,cas=0;
int main()
{
int T;
cin>>T;
while(T--)
{
scanf("%d%d",&n,&s);
int yes=s-2*n,no=3*n-s;
memset(dp,0,sizeof dp);
for(int i=n-1;i>=0;i--)
{
int ma=min(i,yes),mi=max(0,i-no);
for(int j=ma;j>=mi;j--)
{
double p1=(yes-j)*1.0/(n-i),p2=(no-(i-j))*1.0/(n-i);
dp[i%2][j][0]=p1*(dp[(i+1)%2][j+1][0])+p2*(1+dp[(i+1)%2][j][1]);
dp[i%2][j][1]=p2*(dp[(i+1)%2][j][1])+p1*(1+dp[(i+1)%2][j+1][0]);
}
}
printf("Case %d: %.10f\n",++cas,dp[0][0][0]);
}
}
J.很神奇的算贡献利用二项式定理
题意:
在一个三维的空间,每个点都有一盏灯,开始全是关的,
现在每次随机选两个点,把两个点之间的全部点,开关都按一遍;问k次过后开着的灯的期望数量;
题解:
肯定不能从随机抽取两个数这里入手的,要求开着的灯的数量就从对每一盏灯,操作结束后灯开着的概率,然后将这些概率求和就是对于整个矩阵到最后开着的灯的数量了,这就把矩阵的问题落实到了对于求每个坐标的概率的问题。
对每个点单独计算贡献,即k次过后每个点开关被按了奇数次的期望
一个点如果被包到所选空间里,那么说明选的两个点,x坐标在这个点两侧,y坐标在这个点两侧,z坐标在这个点两侧;
对于一维的,可以用1-(x-1)*(x-1)+(n-x)*(n-x)(n*n)
求出两个点在x这一点两侧的概率。然后三维的只要当成三个一维的乘起来就行了。
#include<bits/stdc++.h>
#include<tr1/unordered_map>
const int maxn=5e3+5;
const int mod=1e9+7;
const int INF=0x3f3f3f3f;
const double eps=1e-7;
typedef long long ll;
using namespace std;
double dp[2][maxn][2];
int n,turn,x,y,z,cas=0;
double qpow(double a,int b)
{
double ans=1;
while(b)
{
if(b&1)
ans=ans*a;
a=a*a;
b>>=1;
}
return ans;
}
double cal(int num,int limit)
{
return 1-((num-1)*(num-1)+(limit-num)*(limit-num))*1.0/(limit*limit);
}
int main()
{
int T;
cin>>T;
while(T--)
{
double ans=0;
scanf("%d%d%d%d",&x,&y,&z,&turn);
for(int i=1; i<=x; i++)
{
for(int j=1; j<=y; j++)
{
for(int k=1; k<=z; k++)
{
double p=cal(i,x)*cal(j,y)*cal(k,z);
ans=ans+(1-qpow(1-2.0*p,turn))*0.5;
}
}
}
printf("Case %d: %.10f\n",++cas,ans);
}
}
K.dfs状压dp 还是挺猛的
double 型别memset
有n个街口和m条街道, 你后边跟着警察,需要进行逃亡,在每个街口你都有≥1个选择:
1)停留在原地5分钟。
2)如果这个街口可以到xi这个街口, 并且, 通过xi可以遍历完所有未走过的街口,那么就加入选择。
每个选择都是等概率的,求警察抓住你所用时间的期望, 即你无路可走时的时间期望。
题解:
1. 对于每个点, 先找出所有的选择, 假设有k个选择。
2. 那么E[u] 表示在街口u出发后走完剩余点的时间期望。
则:E[u] = 1 / k * sigma (E[v] + time[u][v]) + 1 / k * (E[u] + 5)。
化简得:E[u] = (sigma (E[v] + time[u][v]) + 5) / (k - 1)
3. 用[S][u]表示从u点出发, 已完成状态为S,之后走完剩余点所用时间的期望。
#include<bits/stdc++.h>
#include<tr1/unordered_map>
using namespace std;
const int maxn=16;
const int mod=1e9+7;
const int INF=0x3f3f3f3f;
const double eps=1e-7;
typedef long long ll;
typedef pair<int,int> p;
int n,m,cas=0,sta;
vector<p>G[maxn];
double dp[maxn][1<<maxn];
int dfs(int u,int S)
{
if(dp[u][S]!=-1)
return dp[u][S]>-1;
if(S==sta)
return dp[u][S]=0,1;
int cnt=0;
double up=0;
for(auto it:G[u])
{
int v=it.first,w=it.second;
if(((S>>v)&1)==1)
continue;
if(dfs(v,S+(1<<v)))
{
cnt++;
up+=(w+dp[v][S+(1<<v)]);
}
}
if(!cnt)
return dp[u][S]=-2,0;
else
return dp[u][S]=(up+5)*1.0/(cnt),1;
}
int main()
{
int T;
cin>>T;
while(T--)
{
scanf("%d%d",&n,&m);
for(int i=0; i<n; i++)
G[i].clear();
for(int i=1; i<=m; i++)
{
int u,v,w;
scanf("%d%d%d",&u,&v,&w);
G[u].push_back({
v,w});
G[v].push_back({
u,w});
}
sta=(1<<n)-1;
for(int i=0;i<=n;i++)
{
for(int j=0;j<=sta;j++)
dp[i][j]=-1;
}
dfs(0,1);
printf("Case %d: %.10f\n",++cas,max(dp[0][1],0.0));
}
}
N.几何分布的一个拓展
地上有n种棍子,
其中有两种类型, 一种类型是可识别, 一种类型是不可识别,
每个棍子都有一个权值。
当你捡到可识别的, 那么你以后就不会再捡这个棍子,
如果是不可识别的, 那么你有可能还会捡。
问将所有棍子收集完的权值的期望。
#include<bits/stdc++.h>
using namespace std;
const int maxn=1e6+100;
const int mod=998244353;
typedef long long ll;
int a[maxn],b[maxn],n,cas=0;
int main()
{
ll T;
cin>>T;
while(T--)
{
double sum1=0,sum2=0;
cin>>n;
for(int i=1; i<=n; i++)
{
scanf("%d%d",&a[i],&b[i]);
if(b[i]==1)
sum1+=a[i];
if(b[i]==2)
sum2+=a[i];
}
for(int i=1;i<=n;i++)
sum1=sum1+sum2/i;
printf("Case %d: %.10f\n",++cas,sum1);
//printf("%.10f\n",sum1);
}
}
O。记忆化搜索:
给你一副扑克牌,54张,
让你求出取出黑梅红方最少分别为a,b,c,d张 的取的次数的期望。
其中大小王可以当做四种牌的任意一种。
题解:dp[a][b][c][d][p][q]表示每种牌分别有a,b,c,d张,
Joker的状态分别作为p和q出现时的期望值
然后记忆化搜索。
#include<bits/stdc++.h>
#include<tr1/unordered_map>
const int maxn=1e3+5;
const int mod=1e9+7;
const int INF=0x3f3f3f3f;
const double eps=1e-7;
typedef long long ll;
using namespace std;
double dp[15][15][15][15][5][5];
int A,B,C,D,cas=0;
void init()
{
for(int i=0; i<=14; i++)
for(int j=0; j<=14; j++)
for(int k=0; k<=14; k++)
for(int a=0; a<=14; a++)
for(int b=0; b<=4; b++)
for(int v=0; v<=4; v++)
dp[i][j][k][a][b][v]=-1;
}
bool judge(int a,int b,int c,int d,int e,int f)
{
if(e==1)
a++;
if(e==2)
b++;
if(e==3)
c++;
if(e==4)
d++;
if(f==1)
a++;
if(f==2)
b++;
if(f==3)
c++;
if(f==4)
d++;
if(a>=A&&b>=B&&c>=C&&d>=D)
return 1;
return 0;
}
double dfs(int a,int b,int c,int d,int e,int f)
{
if(dp[a][b][c][d][e][f]>eps)
return dp[a][b][c][d][e][f];
if(judge(a,b,c,d,e,f))
return dp[a][b][c][d][e][f]=0;
double sum=a+b+c+d+(e!=0)+(f!=0);
if(sum==54)
return dp[a][b][c][d][e][f]=0;
double ans=0;
if(e==0)
{
double mi=1e5+5;
for(int i=1; i<=4; i++)
mi=min(mi,dfs(a,b,c,d,i,f));
ans+=mi;
}
if(f==0)
{
double mi=1e5+5;
for(int i=1; i<=4; i++)
mi=min(mi,dfs(a,b,c,d,e,i));
ans+=mi;
}
if(a<13)
ans=ans+(13-a)*dfs(a+1,b,c,d,e,f);
if(b<13)
ans=ans+(13-b)*dfs(a,b+1,c,d,e,f);
if(c<13)
ans=ans+(13-c)*dfs(a,b,c+1,d,e,f);
if(d<13)
ans=ans+(13-d)*dfs(a,b,c,d+1,e,f);
ans=ans/(54-sum)+1;
return dp[a][b][c][d][e][f]=ans;
}
int main()
{
int T;
cin>>T;
while(T--)
{
int cnt=0;
init();
scanf("%d%d%d%d",&A,&B,&C,&D);
if(A>13)
cnt+=A-13;
if(B>13)
cnt+=B-13;
if(C>13)
cnt+=C-13;
if(D>13)
cnt+=D-13;
if(cnt>2)
printf("Case %d: %.10f\n",++cas,-1.0);
else
printf("Case %d: %.10f\n",++cas,dfs(0,0,0,0,0,0));
}
}