概率和期望
定义
概率,即概率,不提。
期望如下定义:
对于事件
翻译成人话就是事件结果反映权值的平均数。
期望线性性?
不同事件间的期望得以累加,形式化地,
左边的加指的是事件同时成立。
举个例子。事件
事件
由此可以得到:cjx泡妹子看脸两个人每天为人类延续的伟大事业做出的贡献期望
这意味着:处理整个事件的期望时,可以将该事件的子事件按拓扑线性转移。事件的拓扑一般也是线性的。因而导致概率与期望的最好处理方法是 dp。
给定打一把音游的结果,计算成绩。只有 P 和 Miss,还有不知道结果的 note,判定对半开,存在 combo 机制为
这人准度咋这么低啊
思考:如何处理连击分?
如果现在已经有一段长
我们发现不仅要计算期望得分,还得计算期望 combo。
令
注意不得分时是断连,所以
#include<bits/stdc++.h>
#define MAXN 300005
using namespace std;
int n;
char str[MAXN];
double f[MAXN],g[MAXN];
int main(){
scanf("%d",&n);
scanf("%s",str+1);
for(int i=1;i<=n;i++){
if(str[i]=='x')f[i]=f[i-1],g[i]=0;
if(str[i]=='o')f[i]=f[i-1]+2*g[i-1]+1,g[i]=g[i-1]+1;
if(str[i]=='?')f[i]=f[i-1]+1.0*(2*g[i-1]+1)/2,g[i]=1.0*(g[i-1]+1)/2;
}
printf("%.4f",f[n]);
return 0;
}
byd这把准度完全看命了。连击分变成了
同时
所以开三个 dp 数组维护即可。
#include<bits/stdc++.h>
#define MAXN 100005
using namespace std;
int n;
double dp[5][MAXN];
double v[MAXN];
int main(){
scanf("%d",&n);
for(int i=1;i<=n;i++)scanf("%lf",&v[i]);
for(int i=1;i<=n;i++){
dp[1][i]=(1+dp[1][i-1])*v[i];
dp[2][i]=(dp[2][i-1]+2*dp[1][i-1]+1)*v[i];
dp[3][i]=dp[3][i-1]+(3*dp[2][i-1]+3*dp[1][i-1]+1)*v[i];
}
printf("%.1f",dp[3][n]);
return 0;
}
此时事件的拓扑不是线性的了,不过是 DAG,所以直接跑一遍拓扑排序。
注意此题中一个点
所以如果顺推得先处理一遍概率。
#include<bits/stdc++.h>
#define MAXN 100005
using namespace std;
int n,m;
struct node{
int v;
double w;
};
vector<node>edge[MAXN];
int siz[MAXN],ind[MAXN],indd[MAXN];
double p[MAXN],dp[MAXN];
queue<int>Q;
int main(){
scanf("%d%d",&n,&m);
for(int i=1,u,v,w;i<=m;i++){
scanf("%d%d%d",&u,&v,&w);
edge[u].push_back({v,w});
++siz[u],++indd[v],ind[v]=indd[v];
}
p[1]=1.0;
Q.push(1);
while(!Q.empty()){
int u=Q.front();
Q.pop();
for(int i=0;i<edge[u].size();i++){
int v=edge[u][i].v;
double w=edge[u][i].w;
--indd[v];
p[v]+=p[u]/siz[u];
if(!indd[v])Q.push(v);
}
}
Q.push(1);
while(!Q.empty()){
int u=Q.front();
Q.pop();
for(int i=0;i<edge[u].size();i++){
int v=edge[u][i].v;
double w=edge[u][i].w;
--ind[v];
dp[v]+=(dp[u]+p[u]*w)/siz[u];
if(!ind[v])Q.push(v);
}
}
printf("%.2f",dp[n]);
return 0;
}
乍一看很不好做。实则确实不好做
考虑从单个人入手,将人按身高排序作
为了处理方便,令
于是单个人的贡献为:
即前
然后考虑计算这个概率,显然是一个排列组合问题。
假设有
然
然后发现化不动了。
想到:
后面的部分展开:
于是原式化为:
非常简洁。
令
这个题很坑,背包容量不足了其实可以先拿着,只要最后的容量大于等于零即可。
因而当前容量可以为负数,所以把容量挪一下,又注意到最多只会消耗 200 的容量,即
考虑转移,失败时:
成功时:
两者的概率分别为
#include<bits/stdc++.h>
#define MAXN 405
using namespace std;
int n,l,k;
double dp[2][205][MAXN];
int val[MAXN];
double p[MAXN],ans;
int main(){
scanf("%d%d%d",&n,&l,&k);
k=min(n,k);
for(int i=1;i<=n;i++)scanf("%lf",&p[i]),p[i]/=100.0;
for(int i=1;i<=n;i++)scanf("%d",&val[i]);
dp[0][0][n+k]=1;
for(int i=1;i<=n;i++){
for(int j=0;j<=n;j++)
for(int k=0;k<=n*2;k++)dp[i%2][j][k]=dp[(i+1)%2][j][k]*(1-p[i]);
for(int j=0;j<n;j++)
for(int v=1;v<=n*2;v++){
int siz=min(v+val[i],2*n);
dp[i%2][j+1][siz]+=dp[(i+1)%2][j][v]*p[i];
}
}
for(int i=l;i<=n;i++)for(int j=0;j<=n;j++)ans+=dp[n%2][i][j+n];
printf("%.6f",ans);
return 0;
}
这个题会卡空间,注意到当前的
剩下一个人时该人胜率为
由于游戏每轮固定杀掉一个人,所以第
在
又令
对于第
照着打就能过。
#include<bits/stdc++.h>
#define MAXN 55
using namespace std;
int n,m;
double dp[MAXN][MAXN];
int c[MAXN];
int main(){
scanf("%d%d",&n,&m);
for(int i=1;i<=m;i++)scanf("%d",&c[i]);
dp[1][1]=1;
for(int i=2;i<=n;i++){
for(int j=1;j<=i;j++){
for(int k=1;k<=m;k++){
int loc=c[k]%i;
if(!loc)loc=i;
if(loc>j)dp[i][j]+=dp[i-1][i-loc+j]/m;
if(loc<j)dp[i][j]+=dp[i-1][j-loc]/m;
}
}
}
for(int i=1;i<=n;i++)printf("%.2f",dp[n][i]*100),putchar('%'),putchar(' ');
return 0;
}
一道很烦的题,到处都会挂分。
由于不同概率导致的结果会导致路径权值不同,我们使用
如果这次不换:
上次也可能没换,或者换了,然换了要考虑换没换成。
如果这次换了:
那也要同时考虑这次换没换成。
注意:
是浮点类型。- 浮点数不可使用 memset()。
- 有重边自环。
- Floyd 的枚举顺序为
,这个很重要。 时无法转移 。
#include<bits/stdc++.h>
#define MAXN 2005
#define MAXM 305
#define int long long
using namespace std;
int n,m,V,e;
double dis[MAXM][MAXM];
int c[MAXN],d[MAXN];
double p[MAXN];
double dp[MAXN][MAXN][2];
double ans=1e9;
signed main(){
scanf("%lld%lld%lld%lld",&n,&m,&V,&e);
for(int i=0;i<=V;i++)for(int j=0;j<=V;j++)dis[i][j]=1e9;
for(int i=1;i<=n;i++)scanf("%lld",&c[i]);
for(int i=1;i<=n;i++)scanf("%lld",&d[i]);
for(int i=1;i<=n;i++)scanf("%lf",&p[i]);
for(int i=1;i<=V;i++)dis[i][i]=dis[i][0]=dis[0][i]=0;
for(int i=1,u,v,w;i<=e;i++){
scanf("%lld%lld%lld",&u,&v,&w);
dis[u][v]=min(dis[u][v],1.0*w);
dis[v][u]=min(dis[v][u],1.0*w);
}
for(int k=1;k<=V;k++)
for(int i=1;i<=V;i++)
for(int j=1;j<=V;j++){
dis[i][j]=min(dis[i][j],dis[i][k]+dis[k][j]);
dis[j][i]=min(dis[j][i],dis[i][k]+dis[k][j]);
}
for(int i=0;i<=n;i++)for(int j=0;j<=m;j++)dp[i][j][0]=dp[i][j][1]=1e9;
dp[1][1][1]=dp[1][0][0]=0;
for(int i=2;i<=n;i++){
dp[i][0][0]=dp[i-1][0][0]+dis[c[i]][c[i-1]];
for(int j=1;j<=min(i,m);j++){
dp[i][j][0]=dp[i-1][j][0]+dis[c[i]][c[i-1]];
dp[i][j][0]=min(dp[i][j][0],dp[i-1][j][1]+p[i-1]*dis[c[i]][d[i-1]]+(1.0-p[i-1])*dis[c[i]][c[i-1]]);
}
for(int j=1;j<=min(i,m);j++){
dp[i][j][1]=dp[i-1][j-1][0]+p[i]*dis[d[i]][c[i-1]]+(1.0-p[i])*dis[c[i]][c[i-1]];
dp[i][j][1]=min(dp[i][j][1],dp[i-1][j-1][1]+p[i-1]*p[i]*dis[d[i]][d[i-1]]+p[i-1]*(1.0-p[i])*dis[c[i]][d[i-1]]+(1.0-p[i-1])*p[i]*dis[c[i-1]][d[i]]+(1.0-p[i])*(1.0-p[i-1])*dis[c[i]][c[i-1]]);
}
}
for(int i=0;i<=m;i++)ans=min(ans,min(dp[n][i][0],dp[n][i][1]));
printf("%.2f",ans);
return 0;
}
自己想的最多的一道题,虽然最后也颓了下题解。
首先:期望的树上问题大概率用 树形dp 解决,如果去考虑一遍 dfs 预处理后按点间关系实现线性转移大概率要爆。
根据公式:
然
也就是计算每个点被点亮的概率和。
一个点可以这样点亮:
- 自己以
的概率亮了 - 被父亲节点以
的概率传导点亮了 - 被子节点以
的概率传导点亮了
第一条可以初始化时就算好,即
而对于两个点
我们显然不能高效地在 树形dp 中处理这种混乱的关系。而这种关系是由于想要同时考虑
这启示我们分开来看。从任一根节点开始计算,发现
对于节点
不过这个显然是错的,给定
不妨思考
每个子节点
现在单个考虑第三条如何成立。
此时
此时同理得到
如此,使用两遍 dfs 后汇总即可。
但是注意
愤怒的小鸟 里的
#include<bits/stdc++.h>
#define MAXN 500005
using namespace std;
int n;
struct node{
int v;
double p;
};
vector<node>edge[MAXN];
double q[MAXN];
double dp[MAXN],ans;
inline void dfs2(int u,int fa){
for(int i=0;i<edge[u].size();i++){
int v=edge[u][i].v;
double w=edge[u][i].p;
if(v==fa)continue;
dfs2(v,u);
dp[u]=(dp[u]+w*dp[v]-dp[u]*dp[v]*w);
}
}
inline bool check(double val){
return (fabs(val-1.0)<=1e-7);
}
inline void dfs(int u,int fa){
for(int i=0;i<edge[u].size();i++){
int v=edge[u][i].v;
double w=edge[u][i].p;
if(v==fa)continue;
if(!check(dp[v]*w)){
double falink=(dp[u]-w*dp[v])/(1.0-dp[v]*w);
dp[v]=(dp[v]+w*falink-dp[v]*falink*w);
}
dfs(v,u);
}
}
int main(){
scanf("%d",&n);
for(int i=1,u,v;i<n;i++){
double p;
scanf("%d%d%lf",&u,&v,&p);
p/=100.0;
edge[u].push_back({v,p});
edge[v].push_back({u,p});
}
for(int i=1;i<=n;i++)scanf("%lf",&q[i]),q[i]/=100.0;
for(int i=1;i<=n;i++)dp[i]=q[i];
dfs2(1,0);
dfs(1,0);
for(int i=1;i<=n;i++)ans+=dp[i];
printf("%.6f",ans);
return 0;
}
题太吊了。
看了快一个小时一点思路没有,所以直接说题解做法。
首先,对于这样的一个 01串,存在唯一解法使得串能被归零。
解为:从右往左依次扫描,发现开着的灯就关掉。
证明:不难想这一定是可行解,现在要证这样的解法是唯一解。
对于任意一盏灯,其开关次数都不会超过一次,因为这样的操作是取异或,开关两次相当于没动,三次相当于一次。
不妨把一种解法对开关灯的编号汇总为集合
反证,不妨设存在状态
因此,任何情况都有唯一关灯方法即上文提到的方法。
这个结论有什么用呢?这告诉我们,在
也就是说在无穷的随机开关灯中,B 君只有
这不就成概率期望题了?
初始化后,令
则当前情况下:有
整理一下:
我们发现完成归零时的期望转移和状态完全无关了,完美解决了难以设计状态的问题。
又因为剩下
然后就没了。
#include<bits/stdc++.h>
#define MAXN 100005
#define int long long
using namespace std;
const int p=100003;
int f[MAXN];
inline void INIT(){
f[0]=1;
for(int i=1;i<MAXN;i++)f[i]=f[i-1]*i%p;
}
int n,k;
int l[MAXN],val,ans;
int dp[MAXN];
inline int qpow(int base,int power){
int res=1;
while(power){
if(power&1)res=res*base%p;
base=base*base%p;
power>>=1;
}
return res%p;
}
signed main(){
INIT();
scanf("%lld%lld",&n,&k);
for(int i=1;i<=n;i++)scanf("%lld",&l[i]);
for(int i=n;i>=1;i--){
if(l[i]){
++val;
for(int j=1;j*j<=i;j++){
if(i%j==0){
l[j]^=1;
if(j*j!=i)l[i/j]^=1;
}
}
}
}
for(int i=n,tmp=0;i>=1;i--){
tmp=((n-i)*dp[i+1]%p+n)%p;
tmp=tmp*qpow(i,p-2)%p;
dp[i]=tmp;
}
if(val<=k)ans=val;
else{
ans=k;
for(int i=val;i>k;i--)ans=(ans+dp[i])%p;
}
printf("%lld",ans*f[n]%p);
return 0;
}
还有几道难题需要高斯消元,不过我现在不会,此帖暂时完结。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 全程不用写代码,我用AI程序员写了一个飞机大战
· DeepSeek 开源周回顾「GitHub 热点速览」
· 记一次.NET内存居高不下排查解决与启示
· MongoDB 8.0这个新功能碉堡了,比商业数据库还牛
· 白话解读 Dapr 1.15:你的「微服务管家」又秀新绝活了