概率期望学习笔记

概率期望学习笔记


POJ3869

题意:两个人转左轮手枪,朝自己打,枪里保证至少有一个空的,你的对手上一轮活下来了,现在到你了,问重新转左轮和直接打,哪个概率高。

做法:考虑00,10,两种串,即可计算不转时,下一个为空的概率。重新转的概率,就是这个手枪里所有空的位置比所有的口的个数。注意串是循环的。

#include <cstdio>
#include <iostream>
#include <algorithm>
#include <cstring>
#define rep(i,a,b) for(int i=(a);i<=(b);++i)
#define per(i,a,b) for(int i=(a);i>=(b);--i)
#define pb push_back
typedef long long ll;
typedef double LD;
const int N = 2000000;
using namespace std;
char s[N];
int main() {
    scanf(" %s",s+1);
    int n = strlen(s+1),l=1,r=n,num0=0,num1=0;
    rep(ti,1,n) {
        if(s[r-1]=='0'&&s[r]=='0')++num0;
        if(s[r-1]=='1'&&s[r]=='0')++num1;
        ++r; s[r]=s[l]; ++l;
    }
    if(num0*n>(num1+num0)*(num1+num0))puts("SHOOT");
    else if(num0*n<(num1+num0)*(num1+num0))puts("ROTATE");
    else puts("EQUAL");
    return 0;
}

POJ2794

题意:有9组牌,每组4张,两张牌的大小一样时可以消掉,每次只能消掉最上面的牌,然后一个在玩的时候如果有多种消法,就会随机选择其中一种,问他将所有牌都消掉的几率是多少。

做法:用一个9位5进制数表示当前的状态,直接记忆化搜索即可。一开始的写法T了。

#include <cstdio>
#include <iostream>
#include <algorithm>
#include <cstring>
#define rep(i,a,b) for(int i=(a);i<=(b);++i)
#define per(i,a,b) for(int i=(a);i>=(b);--i)
#define pb push_back
typedef long long ll;
typedef double LD;
const int N = 2000000;
using namespace std;
int a[20][20], M[266], num[9], A[9], f[22], ST, vis[N];
LD dp[N];
char s1[4];
double solve(int s) {
    if(vis[s]) return dp[s];
    vis[s]=1;
    int A[9],tmp=0;
    rep(i,0,8) A[i]=(s/f[i])%5;

    rep(i,1,8)rep(j,0,i-1) {
        if(A[i]&&A[j]&&a[i][A[i]]==a[j][A[j]]) {
            dp[s] += solve(s-f[i]-f[j]);
            ++tmp;
        }
    }
    if(tmp)dp[s]/=(double)tmp;
    return dp[s];
}

int main() {
    f[0]=1; rep(i,1,9)f[i]=f[i-1]*5;
    ST = f[9]-1;

    M['6']=0;M['7']=1;M['8']=2;M['9']=3;M['T']=4;
    M['J']=5;M['Q']=6;M['K']=7;M['A']=8;

    rep(i,0,8)rep(j,1,4) {
        scanf(" %s",s1);
        a[i][j] = M[s1[0]];
    }
    vis[0]=1; dp[0]=1.0;
    cout << solve(ST) << '\n';

//TLE
//    memset(dp,0,sizeof(dp));
//    int x,y,t,z,tmp;LD tt;
//    dp[ST]=1.0;
//    per(s,ST,0) {
//        z = 0;tmp=0;
//        rep(i,0,8) {
//            x = (s/f[i])%5;
//            if(num[a[i][x]]==0)num[a[i][x]]=1,++tmp;
//            z+=x;
//        }
//        memset(num,0,sizeof(num));
//        if(tmp==9)continue;
//        if(z&1)continue;
//        tmp=0;
//        rep(j,0,8) {x = (s/f[j])%5; if(x>0)++num[a[j][x]];}
//        rep(j,0,8) if(num[j]>=2) tmp += (1*(num[j]*(num[j]-1))>>1);
//        tt = (LD)dp[s]/tmp;
//
//        rep(j,0,8) A[j]=(s/f[j])%5;
//        rep(j,1,8)rep(k,0,j-1) {
//            x=A[k], y=A[j];
//            if(x!=0&&y!=0&&a[j][y]==a[k][x]){
//                t = s; t-=(f[k]+f[j]);
//                dp[t] += tt;
//            }
//        }
//    }
//    printf("%f\n",(double)dp[0]);
    return 0;
}

Codeforces1009E

题意:给定序列a,要求把长度n,划分成一些段,每一段的值形如a1,a2,..,求这些值和的期望。

做法:考虑每一个位置的期望。对于位置i,值为a1,只有它的前面恰好是一个分界线;值为a2,即他向前一个的位置恰好是个分界线;同理,可以列数位置i的值的期望为:
\(E_i = \frac{a_1}{2^{1}} + \frac{a_2}{2^{2}} + ... + \frac{a_{i-1}}{2^{i-1}} + \frac {a_i}{2^{i-1}}\)
\(E_1 = a_1\)
\(E_2 = \frac{a_1}{2^{1}} + \frac {a_2}{2^{1}}\)
\(E_3 = \frac{a_1}{2^{1}} + \frac{a_2}{2^{2}} + \frac {a_3}{2^{2}}\)
$E_4 = \frac{a_1}{2^{1}} + \frac{a_2}{2^{2}} + \frac {a_3}{2^{3}} + \frac {a_4}{2^{3}} $
直接求和即可。

#include <bits/stdc++.h>
#define rep(i,a,b) for(int i=a;i<=b;++i)
#define per(i,a,b) for(int i=a;i>=b;--i)
#define pb push_back
typedef long long ll;
const int N = 1e6 + 7;
const ll mod = 998244353;
using namespace std;
int n;
ll a[N],ans=0,f[N];

int main() {
    scanf("%d",&n);
    f[0]=1;
    rep(i,1,n)f[i]=(f[i-1]*2LL)%mod;
    rep(i,1,n) scanf("%I64d",&a[i]);
    rep(i,1,n) {
        ll t;
        if(i<n) t = ((a[i]*f[n-i])%mod + (a[i]*((f[n-i-1]*(n-i))%mod))%mod)%mod;
        else t = (a[i]*f[n-i])%mod;
        ans=(ans+t%mod)%mod;
    }
    printf("%I64d\n",ans);
    return 0;
}

Codeforces930B

题意:给定一个串,对他循环移位,通过首字母和一个后边的字母判断移了多少位,问成功的概率。

做法:对每种移位情况下,枚举每次去查哪个位置,对每种字母找到最好的位置。即使得尽可能多的情况下,这个位置的字母是不同的,使得成功的概率更高。设字母\(c_i\)的最优的情况后边有\(num_i\)个位置,字母唯一,\(c_i\)的个数为\(cnt(c_i)\),所以以这种字母开头,成功概率为\(\frac{num_i}{cnt(c_i)}\),把所有字母累加起来就是:\(\sum {\frac{num_i}{cnt(c_i)}·\frac {cnt(c_i)}{n}} = \frac {\sum {num_i}}{n}\)

#include <bits/stdc++.h>
#define rep(i,a,b) for(int i=a;i<=b;++i)
#define per(i,a,b) for(int i=a;i>=b;--i)
#define pb push_back
typedef long long ll;
const int N = 5005;
using namespace std;
int n,ff[N],num[26];
char s[N<<1];
vector<int> v[26];
int main() {
    scanf(" %s",s+1);
    n = strlen(s+1);
    rep(i,1,n) s[i+n]=s[i];
    rep(i,1,n) v[s[i]-'a'].pb(i);
    rep(i,0,25)rep(x,1,n-1){int f=0;
        memset(num,0,sizeof(num));
        for(auto p: v[i]) ++num[s[p+x]-'a'];
        int tt = 0;
        rep(j,0,25)if(num[j]==1)++tt;
        ff[i]=max(ff[i],tt);
    }
    int tmp = 0;
    rep(i,0,25)tmp+=ff[i];
    printf("%.10f\n",1.0*tmp/(double)n);
    return 0;
}

Codeforces935D

题意:给定两个序列S1, S2,他们有一些位置的值是确定的,一些是不确定的,可以填入1~m中的数,问S1比S2字典序高的概率。

做法:从高位到低位递推,dp[i][0]表示前i个位置S1等于S2的概率,dp[i][1]表示前i个位置,S1大于S2的概率。分情况讨论,直接做即可。

#include <bits/stdc++.h>
#define rep(i,a,b) for(int i=a;i<=b;++i)
#define per(i,a,b) for(int i=a;i>=b;--i)
#define pb push_back
typedef long long ll;
const int N = 1e5 + 7;
const int mod = 1e9 + 7;
using namespace std;
ll n,m,a[N],b[N],dp[N][2],invm,invmm,inv2;
ll q_pow(ll a,ll b) {
    a%=mod;
    ll ans = 1;
    while(b) {
        if(b&1) ans=(ans*a)%mod;
        a=(a*a)%mod;
        b>>=1;
    }
    return ans;
}
ll P(int i,int opt) {
    if(!opt) {
        if(a[i]&&b[i]&&a[i]==b[i]) return 1LL;
        if(a[i]&&b[i]&&a[i]!=b[i]) return 0LL;
        if(!a[i]&&b[i]) return invm%mod;
        if(a[i]&&!b[i]) return invm%mod;
        if(!a[i]&&!b[i]) return invm%mod;
    }
    else {
        if(a[i]&&b[i]&&a[i]>b[i]) return 1LL;
        if(a[i]&&b[i]&&a[i]<=b[i]) return 0LL;
        if(!a[i]&&b[i]) return ((m-b[i])%mod*invm)%mod;
        if(a[i]&&!b[i]) return ((a[i]-1LL)%mod*invm)%mod;
        if(!a[i]&&!b[i]) return ((((((m-1LL)%mod)*m)%mod*inv2)%mod)*invmm)%mod;
    }
}

int main() {
    scanf("%I64d%I64d",&n,&m);
    inv2 = q_pow(2LL,mod-2);
    invm = q_pow(m,mod-2);
    invmm = (invm*invm)%mod;
    rep(i,1,n)scanf("%I64d",&a[n-i+1]);
    rep(i,1,n)scanf("%I64d",&b[n-i+1]);
    dp[1][0]=P(1,0), dp[1][1]=P(1,1);
    rep(i,2,n) {
        dp[i][0] = (P(i,0)*dp[i-1][0])%mod;
        dp[i][1] = (P(i,1) + (P(i,0)*dp[i-1][1])%mod)%mod;
    }
    (dp[n][1]+=mod)%=mod;
    printf("%I64d\n",dp[n][1]);
    return 0;
}

Codeforces280C

题意:给定一棵树,每次操作可以删除一颗子树。问期望的操作次数。

题解:考虑每个点对答案的贡献,点i最终一定会被删除,如果删除它的时候,是在删除它的祖先,则他对答案无贡献,如果是通过自己删的,则对答案有贡献。能删除它的祖先共有:这个点的深度-1 个点。所以点u的期望就是\(\frac{1}{deep[u]}\),最后累加起来。一开始,推出了深度是1的树的公式,然后推广了一下,写的自底向上算。。结果gg,这题思路真的有点神。

#include <bits/stdc++.h>
#define rep(i,a,b) for(int i=a;i<=b;++i)
#define per(i,a,b) for(int i=a;i>=b;--i)
typedef long long ll;
const int N = 1e5 +7;
using namespace std;
struct edge{int e,nxt;}E[N<<1];
int cc,h[N];
void add(int u,int v) {E[cc].e=v;E[cc].nxt=h[u];h[u]=cc;++cc;}
int n,dep[N];
double ans = 0;
void dfs(int u,int pre) {
    ans += 1.0/dep[u];
    for(int i=h[u];~i;i=E[i].nxt) if(E[i].e!=pre) {
        dep[E[i].e] = dep[u] + 1;
        dfs(E[i].e,u);
    }
}
int main() {
    scanf("%d",&n);
    rep(i,1,n) h[i] = -1;
    rep(i,1,n-1) {int x,y;
        scanf("%d%d",&x,&y);
        add(x,y), add(y,x);
    }
    dep[1] = 1; dfs(1,0);
    printf("%.12f\n",ans);
    return 0;
}

Codeforces452C

题意:把n副一样的牌混在一起,每副牌包含m张,从其中选n张,问取两次牌相同的概率。

做法:考虑一种牌x的贡献,枚举x的个数k,现在先从n*m张牌中挑n张,其中有k张x,然后再考虑从中两次选出x的概率,可以列出式子:$$\sum_{k=1}^{min(m,n)} \frac {Ck_m·C_{nm-m}·k2}{C_{nm}n·n}$$

#include <bits/stdc++.h>
#define rep(i,a,b) for(int i=a;i<=b;++i)
#define per(i,a,b) for(int i=a;i>=b;--i)
typedef long long ll;
const int N = 1e5 +7;
using namespace std;
int n,m;
int main() {
    scanf("%d%d",&n,&m);
    double Cnmn = 1.0, Cmx = m, Cnm_x = 1.0, ans = 0;
    rep(i,1,n) Cnmn = Cnmn * (n*m - i + 1)/i;
    rep(i,1,n-1) Cnm_x = Cnm_x * ((n-1.0)*m - i + 1)/i;
    rep(i,1,min(n,m)) {
        ans += Cmx*Cnm_x*i*i/Cnmn/n;
        Cmx = Cmx * (m - (i+1) + 1.0)/(i+1);
        Cnm_x = Cnm_x * (n-(i+1)+1)/((n-1.0)*m - (n-(i+1)+1) + 1);
    }
    printf("%.10f\n",ans);
    return 0;
}

Codeforces261B

\(f[i][j][k]\)表示前i个元素取j个,构成体积k的方法数,枚举最后放不进去的元素是哪个,每种情况对答案的贡献,就是\(\sum {f[n][j][k]·j!·(n-j-1)!}\),计算f[i][j][k]的时候记得不要放x,最后的答案除\(n!\),和背包一样,\(i\) 这一维可以去掉,j和k都相当于一种体积。。。智商逐渐消失。。


#include <bits/stdc++.h>
#define rep(i,a,b) for(int i=a;i<=b;++i)
#define per(i,a,b) for(int i=a;i>=b;--i)
#define pb push_back
typedef long long ll;
const int N = 52;
using namespace std;
int n,a[N],p,sum;
double f[N][N], ans, fc[N]; // (前i个元素)取j个,构成体积k的方法数

int main() {
    scanf("%d",&n);
    rep(i,1,n) scanf("%d",&a[i]),sum+=a[i];
    scanf("%d",&p);
    fc[0] = 1.0;
    rep(i,1,n) fc[i]=fc[i-1]*i;
    if(sum <= p) {
        printf("%.10f\n",(double)n);
    }
    else {
        rep(x,1,n) { // 枚举那个恰好没被使用的元素
            memset(f,0,sizeof(f));
            rep(i,0,n) f[0][0] = 1;
            rep(i,1,n) {
                per(k,p,a[i]) per(j,i,1) {
                    if(i!=x)f[j][k] += f[j-1][k-a[i]];
                }
            }
            rep(i,0,n-1) rep(k,max(p-a[x]+1,0),p) {
                ans += f[i][k]*fc[i]*fc[n-i-1]*i;
            }
        }
        ans /= fc[n];
        printf("%.10f\n",ans);
    }
    return 0;
}

posted @ 2018-07-29 16:03  RRRR_wys  阅读(208)  评论(0编辑  收藏  举报