Codeforces 期望概率dp 235B,351B,768D,50D,101D 题解


5道不错的概率dp.

235B Let’s Play Osu!

你 打 音 游 的 得 分 是 你 每 次 击 中 连 续 音 符 个 数 的 平 方 和 . 给 出 你 击 中 每 一 个 音 符 的 概 率 , 求 你 的 期 望 得 分 . 你打音游的得分是你每次击中连续音符个数的平方和.\newline 给出你击中每一个音符的概率,求你的期望得分. .,.
考虑总贡献是两个击中音符的对数加上击中音符的个数.
每一个音符能够对两个击中音符的贡献 d p [ i ] = ( d p [ i − 1 ] + 1 ) × p [ i ] dp[i]=(dp[i-1]+1)\times p[i] dp[i]=(dp[i1]+1)×p[i].
i i i个音符能够对得分的贡献 s u m [ i ] = ( s u m [ i − 1 ] + d p [ i ] × 2 + 1 ) × p [ i ] sum[i]=(sum[i-1]+dp[i]\times2+1)\times p[i] sum[i]=(sum[i1]+dp[i]×2+1)×p[i].
最后的答案就是 s u m [ n ] sum[n] sum[n].

#include<bits/stdc++.h> //Ithea Myse Valgulious
using namespace std;
const int yuzu=1e5;
typedef double db;
int main() {
    
    
int i,n=read(); 
db llx=0,zxy=0,x;
for (i=1;i<=n;++i) 
  scanf("%lf",&x),
  llx+=x*(1+zxy*2),
  zxy=x*(zxy+1);
printf("%.12lf\n",llx);
}

351B Jeff and Furik

一 个 排 列 , 每 次 逆 序 对 个 数 先 减 少 一 , 然 后 以 1 2 的 几 率 增 加 一 或 者 减 少 1. 求 到 达 逆 序 对 个 数 为 0 的 期 望 次 数 . 一个排列,每次逆序对个数先减少一,然后以\frac{1}{2}的几率增加一或者减少1.\newline 求到达逆序对个数为0的期望次数. ,,211.0.
结论题.每两次操作的逆序对期望减少个数等于 1 1 1.
答案就是逆序对的个数乘以 2 2 2.
注意如果逆序对有奇数个,在最后一次操作会直接排序完毕,因此要减去1.

const int aoi=3058;
int a[aoi];
int main() {
    
    
int i,j,n=read(),ans=0;
for (i=1;i<=n;++i)
  for (a[i]=read(),j=1;j<i;++j)
    ans+=a[j]>a[i];
write(ans*2-ans%2);
}

768D Jon and Orbs

有 k 种 物 品 , 每 次 等 概 率 获 得 其 中 一 种 , 求 获 得 全 部 物 品 的 概 率 大 于 等 于 p − 1 0 − 7 2000 的 期 望 次 数 . 有k种物品,每次等概率获得其中一种,求获得全部物品的概率大于等于\frac{p-10^{-7}}{2000}的期望次数. k,,2000p107.
暴力进行期望 d p dp dp求出操作 j j j次获得 i i i种物品的概率.
然后暴力跑每一组询问,答案最大是 7000 7000 7000多的一个数.
不行的话二分吧,也不难写.

const int aoi=1058;
typedef double db;
const db eps=1e-7;
db dp[aoi*8][aoi];
/*dp[i][j]表示取i次球,获得j种的概率.*/
int main() {
    
    
int k,q,i,j;
read(k),read(q);
for (**dp=i=1;i<=7274;++i) {
    
     //答案最大7274
  for (j=1;j<=k;++j) {
    
    
    dp[i][j]=dp[i-1][j]*j/k+dp[i-1][j-1]*(k-j+1)/k;
    }
  }
for (;q--;) {
    
    
  db p=(read()*1.0-eps)/2000;
  for (i=1;i<=7274&&dp[i][k]<p;++i);
  write(i),pl;
  }
}

50D Bombing

给 出 n 座 城 市 和 核 弹 爆 炸 点 , 给 出 核 弹 爆 炸 半 径 和 建 筑 物 离 爆 炸 点 距 离 与 建 筑 物 被 毁 概 率 的 关 系 , 求 想 要 至 少 炸 掉 k 座 城 市 失 败 的 概 率 小 于 p 核 弹 的 最 小 爆 炸 半 径 . 给出n座城市和核弹爆炸点,给出核弹爆炸半径和建筑物离爆炸点距离\newline 与建筑物被毁概率的关系,求想要至少炸掉k座城市失败的概率小于p核弹的最小爆炸半径. n,,kp.
显然有单调性,二分答案.
对于二分出的答案,轻松进行期望 d p dp dp, d p [ i ] [ j ] dp[i][j] dp[i][j]表示前 i i i座城市被炸掉 j j j座的概率.
最后被炸掉 0 → k − 1 0\to k-1 0k1座都是失败的,把这些概率加起来与给定失败概率比较.
这概率公式牛逼.

const int z=233;
typedef double db;
const db e=2.7182818284590452353602874713527;
int n,x[z],y[z],cx,cy,k;
db fail,dp[z][z];

db dis(int a,int b,int k) {
    
    return sqrt((a-x[k])*(a-x[k])+(b-y[k])*(b-y[k]));}
bool akioi(db br) {
    
    
db t; int i,j;
memset(dp,0,sizeof dp);
for (**dp=i=1;i<=n;++i) 
  for (dp[i][0]=dp[i-1][0]*((t=dis(cx,cy,i))<=br?0:(1-pow(e,1-t*t/br/br))),j=1;j<=n;++j) 
    dp[i][j]=(t=dis(cx,cy,i))<=br?dp[i-1][j-1]:dp[i-1][j]*(1-pow(e,1-t*t/br/br))+dp[i-1][j-1]*pow(e,1-t*t/br/br);
db llx=0;
for (i=0;i<k;++i) llx+=dp[n][i];
return llx>fail;
}

int main() {
    
    
read(n); int i,t;
k=read(),fail=1.0*read()/1000;
cx=read(),cy=read();
for (i=1;i<=n;++i) x[i]=read(),y[i]=read();
db l=0,r=1e5,mid;
for (t=100;t--;akioi(mid)?l=mid:r=mid) mid=(l+r)/2;
printf("%.12lf\n",r);
}

101D Castle

你 在 树 的 1 号 节 点 , 树 上 每 条 边 有 经 过 的 时 间 , 而 且 最 多 只 能 经 过 两 次 . 树 上 除 了 1 号 节 点 每 一 个 节 点 都 可 能 有 宝 物 , 你 走 到 有 宝 物 的 节 点 会 停 下 来 . 求 你 获 得 宝 藏 走 的 距 离 的 期 望 . 你在树的1号节点,树上每条边有经过的时间,而且最多只能经过两次.\newline 树上除了1号节点每一个节点都可能有宝物,你走到有宝物的节点会停下来.\newline 求你获得宝藏走的距离的期望. 1,,.1,..
首先这题不是 d p dp dp.不是 d p ! dp! dp!
我们需要考虑的是当前如果走到 u u u的子树,我们接下来应该怎么走是最优的.
由于时间原因,不给出证明了,可以直接看题解.
就是对 c o s t ( f a [ u ] , u ) + l e n [ u ] s z [ u ] \frac{cost(fa[u],u)+len[u]}{sz[u]} sz[u]cost(fa[u],u)+len[u] u u u到父亲的距离加上 u u u子树里所有的边权和除以 u u u子树的大小从小到大排序.
最后直接考虑每一个子树的贡献.

#include<bits/stdc++.h> //Ithea Myse Valgulious
namespace chtholly{
    
    
typedef long long ll;
#define re0 register int
#define rel register ll
#define rec register char
#define gc getchar
//#define gc() (p1==p2&&(p2=(p1=buf)+fread(buf,1,1<<23,stdin),p1==p2)?-1:*p1++)
#define pc putchar
#define p32 pc(' ')
#define pl puts("")
/*By Citrus*/
char buf[1<<23],*p1=buf,*p2=buf;
inline int read(){
    
    
  int x=0,f=1;char c=gc();
  for (;!isdigit(c);c=gc()) f^=c=='-';
  for (;isdigit(c);c=gc()) x=(x<<3)+(x<<1)+(c^'0');
  return f?x:-x;
  }
template <typename mitsuha>
inline bool read(mitsuha &x){
    
    
  x=0;int f=1;char c=gc();
  for (;!isdigit(c)&&~c;c=gc()) f^=c=='-';
  if (!~c) return 0;
  for (;isdigit(c);c=gc()) x=(x<<3)+(x<<1)+(c^'0');
  return x=f?x:-x,1;
  }
template <typename mitsuha>
inline int write(mitsuha x){
    
    
  if (!x) return 0&pc(48);
  if (x<0) pc('-'),x=-x;
  int bit[20],i,p=0;
  for (;x;x/=10) bit[++p]=x%10;
  for (i=p;i;--i) pc(bit[i]+48);
  return 0;
  }
inline char fuhao(){
    
    
  char c=gc();
  for (;isspace(c);c=gc());
  return c;
  }
}using namespace chtholly;
using namespace std;
const int yuzu=1e5;
typedef ll fuko[yuzu|10];
fuko sz,dp,len;
/*sz表示子树大小,len表示这个点到它父亲长度的两倍加上它整棵子树的边之和.dp表示这一个点到它的子树获得宝物所用时间的期望.*/
struct edge{
    
    int v,c;};
vector<edge> lj[yuzu|10];

void dfs(int u,int fa) {
    
    
  vector<int> stk; sz[u]=1;
  for (edge i:lj[u]) {
    
    
    int v=i.v,c=i.c;
    if (v^fa) {
    
    
      dfs(v,u),sz[u]+=sz[v],len[u]+=(len[v]+=(c<<1));
      dp[u]+=dp[v]+sz[v]*c,stk.push_back(v);
      /*v子树走过的每一个节点都要算上从u到v这条边的长.*/
    }   
  }
  sort(stk.begin(),stk.end(),[](int a,int b){
    
    return sz[a]*len[b]>sz[b]*len[a];});
  ll llx=0;
  for (int v:stk) dp[u]+=sz[v]*llx,llx+=len[v];
  /*走过前面的所有子树用掉的时间和*/
}

int main() {
    
    
  int i,n,u,v,c;
  for (read(n),i=1;i<n;++i) {
    
    
    read(u),read(v),read(c),
    lj[u].push_back(edge{
    
    v,c}),
    lj[v].push_back(edge{
    
    u,c});
  } 
  dfs(1,0);
  printf("%.12lf\n",1.0*dp[1]/(n-1));
}
/*
在走到一个点的时候,我们需要确定先走哪一颗子树获得宝藏的概率最大.
这是一个贪心.
我们把子树按照子树里的所有边长度(包括父节点连到子树里的那一条边)/子树大小从小到大排序.
*/

谢谢大家.

猜你喜欢

转载自blog.csdn.net/qq_31908675/article/details/83795078
今日推荐