lightoj 概率dp小结

**马上要去上海邀请赛啦,为了不给老万和聪神拖后腿,我就翘课刷了点题。
不过聪神让我学学数学,然后我昨天和今天刷了一天的概率dp,好像偏题了。。。**

题目:kuangbin带你飞 概率与期望(貌似是lightoj上的)
地址:点击打开链接
A 题:
大意:一个人不小心掉进了一个迷宫里,然后有n个门,分为两类,第一类进去之后就可以花费x_i的时间出去,数量为a;第二类进去之后花费x_i的时间回到原位,数量为b。问走出迷宫的期望。
思路:大概是我太菜了,刚一开始,我就列了一串公式,一个特别奇葩的数列的求和,幸好高中的时候做过这种练习题,化简了一节课得到了一个公式特别简单。就是对x_i求和除以a。然后后两节课老师要查人我就去上课了,然后上课的时候发现可以列如下方程。p=sum(ai)/n+sum(bi)/n+b/n*p;
代码:

#include <cstdio>
#include <cmath>
using namespace std;
int gcd(int a,int b){
    return b==0?a:gcd(b,a%b);
}
int main(){
    int T,n,cas=0;
//  freopen("data.in","r",stdin);
    scanf("%d",&T);

    while(T--){
        printf("Case %d: ",++cas);
        scanf("%d",&n);
        int fz=0,fm=0,tp;
        for(int i=0;i<n;i++){
            scanf("%d",&tp);
            if(tp>0){
                fm++;
            }
            fz+=abs(tp);
        }
        if(fm==0){
            puts("inf");
            continue;
        }
        tp=gcd(fz,fm);
        printf("%d/%d\n",fz/tp,fm/tp);
    }
    return 0;
}

B题:
题意:
给一个一维的长度为n的方格,骰骰子决定下一次的位置,每一个位置都有w_i的黄金,走到n为止,如果越界重新骰。求得到黄金的期望。
思路:
尼玛做到这个题的时候我才发现好像跑题了。。。聪神让我学数学啊,但是这不是dp吗?肥不肥挨打。。。dp[i]为从i点开始得到黄金的期望,每一点的状态由后面的6个点等概率倒推,然后我写的记忆化搜索。
代码:

#include <cstdio>
#include <iostream>
#include <cstring>
using namespace std;
int n;
int data[110];
double dp[110];
double get(int a){
    if(dp[a]>0) return dp[a];
    double ret=data[a];
    if(a+6<n){
        for(int i=1;i<=6;i++){
            ret+=get(a+i)/6;
        }
    }
    else {
        int tp=n-a;
        for(int i=1;i<=tp;i++){
            ret+=get(a+i)/tp;
        }

    }
    //cout<<ret<<" "<<a<<endl;
    return dp[a]=ret;
}
int main(){
    int T,cas=0;
    //freopen("data.in","r",stdin);
    scanf("%d",&T);
    while(T--){
        printf("Case %d: ",++cas);
        scanf("%d",&n);
        for(int i=1;i<=n;i++){
            scanf("%d",data+i);
            dp[i]=-1;
        }
        printf("%.9f\n",get(1));
    }
    return 0;
}

C 题:
题意:给定n,每次选一个n的因子除n,问n==1的期望步数。
思路:先预处理出每一个数的因子,然后dp,dp[i]表示分解i的期望,然后根据a题我手动推的公式扩展一下就可以得出dp[i]等于i的所有包含因子的期望的和加上因子的个数除以因子的个数减一。由于太菜不会写递推,只好搜索了。。。
代码:

#include <cstdio>
#include <iostream>
#include <cstring>
#include <vector>
#include <cmath>
using namespace std;
int n;
int data[110000];
double dp[110000];
vector<int> fac[100010];
void factor(int a){
    for(int i=1;i<=sqrt(a);i++){
        if(a%i==0){
            fac[a].push_back(i);
            if(i!=a/i)
            fac[a].push_back(a/i);
        }
    }
}
double get(int a){
    if(dp[a]>=0) return dp[a];
    double ret=0;
    for(int i=1;i<fac[a].size();i++){
        ret+=get(a/fac[a][i]);

    }
    ret+=fac[a].size();
    ret/=(double)(fac[a].size()-1);

    return dp[a]=ret;
}
void prepare(){
    for(int i=1;i<100001;i++){
        factor(i);
    }
}
int main(){
    int T,cas=0;
    prepare();
    for(int i=1;i<100005;i++){
        dp[i]=-1;
    }
    dp[1]=0;
    //freopen("data.in","r",stdin);
    scanf("%d",&T);
    while(T--){
        scanf("%d",&n);
        printf("Case %d: %.10f\n",++cas,get(n));
    }
    return 0;
}

要上课了我先睡会。。。

继续写。。头有点晕。。。
D题:
题意:一个歹徒抢银行,但是他有点胆小,被抓的概率高于p它就不干了。。。
题目给出每个银行被抓的概率p_i,和里面的钱v_i,问在概率低于p的时候最多能抢到多少钱。
思路:类似于01背包。dp[i][j]表示前i个银行抢到j元的最低概率,转移方程为dp[i][j]=min(dp[i-1][j],dp[i-1][j-v_i]*(1-p_i)+p_i),由于我会写01背包,终于可以递推写啦~
代码:

#include <cstdio>
#include <iostream>
#include <cstring>
#include <algorithm>
using namespace std;
const int maxn= 10110;
int n,data[110];
double dp[maxn],p,w[110];
int main(){
    int T;
    int cas=0;
    //freopen("data.in","r",stdin);
    scanf("%d",&T);
    while(T--){

        int ans=0;
        for(int i=0;i<maxn;i++) dp[i]=1000000000;
        dp[0]=0;
        scanf("%lf%d",&p,&n);
        for(int i=0;i<n;i++){
            scanf("%d%lf",&data[i],&w[i]);
            for(int j=10000;j>=0;j--){
                if(j>=data[i]){
                    dp[j]=min(dp[j-data[i]]+(1-dp[j-data[i]])*w[i],dp[j]);
                    if(dp[j]<p){
                        ans=max(j,ans);

                    }
                }
            }
        }
        printf("Case %d: %d\n",++cas,ans);
    }
    return 0;
}

E题:
题意:从1~n随机选k个整数,使得出现重复的数字的概率大于等于0.5,问k最小为多少。
思路:可知不出现重复数字的概率是随k单调递减的,然后从1~n开始暴力求解最小的k使得不出现重复数字的概率小于等于0.5。
代码:

#include <cstdio>
#include <iostream>
#include <cstring>
#include <algorithm>
using namespace std;
const int maxn= 10110;
//const double eps=1e-8;
int n,data[110];
double dp[maxn],p,w[110];
int main(){
    int T;
    int cas=0;
    //freopen("data.in","r",stdin);
    scanf("%d",&T);
    while(T--){

        double ans=1;
        scanf("%d",&n);

        int i;
        for(i=0;i<=n&&ans>0.5;i++){
            ans*=(double)(n-i)/(double)(n);
            //cout<<ans<<" "<<i+1<<endl;
        }
        i--;
    //  if(n==2) i=2;
        printf("Case %d: %d\n",++cas,i);
    }
    return 0;
}

F题:
题意:1~100个格子,类似于b题,但是会有传送门,问到达100的骰骰子期望次数。
思路:dp[i]表示在位置i的期望步数,值得注意的是若i+6>100,则i~i+k的期望步数为6/(100-i);又因为传送门的存在dp[i]=dp[go[i]],存在环,无法手动解出关系,所以应该用高斯消元。
代码:

#include <cstdio>
#include <iostream>
#include <cstring>
#include <queue>
#include <cmath>
using namespace std;
const int maxn =110;
const double EPS=1e-9;
double a[maxn][maxn],ans[maxn];
bool l[maxn];
const int max_n=100;
inline int solve(int n){
    int res=0,r=0;
    for(int i=0;i<n;i++)
        l[i]=false;
    for(int i=0;i<n;i++){

        for(int j=r;j<n;j++){

            if(fabs(a[j][i])>EPS){

                for(int k=i;k<=n;k++)
                    swap(a[j][k],a[r][k]);

                break;

            }
        }

        if(fabs(a[r][i])<EPS){
            ++res;
            continue;
        }
        for(int j=0;j<n;j++){
            if(j!=r&&fabs(a[j][i])>EPS){
                double tmp=a[j][i]/a[r][i];
                for(int k=i;k<=n;k++){
                    a[j][k]-=tmp*a[r][k];
                }
            }
        }
        l[i]=true,++r;
    }

    for(int i=0;i<n;i++){
        {
            for(int j=0;j<n;j++)
            {
                if(fabs(a[j][i])>0)
                    ans[i]=a[j][n]/a[j][i];
            }
        }
    }
    return res;
}
int data[maxn][maxn],cnt[maxn];
void prepare(){
    memset(data,0,sizeof(data));
    memset(a,0,sizeof(a));
    for(int i=1;i<=max_n;i++){
        cnt[i]=0;
        for(int j=1;j<=6;j++){
            if(i+j<=max_n){
                data[i][i+j]=1;
                cnt[i]++;
            }
        }
    }
}
void debug(){
    for(int i=0;i<max_n;i++){
        printf("%2d ",i+1);
        for(int j=0;j<=max_n;j++){
            //printf("[%3d][%3d]%5f  ",i+1,j+1,a[i][j]);
            printf("%2d",(int)a[i][j]);
        }
        puts("");
    }
}
int main(){
    int T,n,cas=0;


    //freopen("data.in","r",stdin);
    //freopen("data.out","w",stdout);
    scanf("%d",&T);
    while(T--){
        scanf("%d",&n);
        int ae,be;
        printf("Case %d: ",++cas);
        prepare();

        for(int i=1;i<=max_n;i++){
            a[i-1][i-1]=cnt[i];a[i-1][max_n]=6;
            for(int j=1;j<=max_n;j++){
                if(data[i][j]){
                    a[i-1][j-1]=-1;
                }
            }
        }
        for(int i=0;i<n;i++){
            scanf("%d%d",&ae,&be);
            swap(ae,be);
            for(int j=0;j<=100;j++){
                a[be-1][j]=0;
            }
            a[be-1][ae-1]=-1;
            a[be-1][be-1]=1;
        }
//      for(int i=0;i<=max_n;i++) a[0][i]=0;
        a[99][99]=1;a[99][100]=0;
        //debug();
        solve(max_n);
        printf("%.10f\n",ans[0]);

    }
    return 0;
}

G题:
题意:从1~n又放回的选数,问选满1~n的期望。
思路:dp[i]表示已经选了i个数的期望次数。转移方程还是由 A题公式拓展。
dp[i]=((n-i+1)*dp[i-1]+n)/(n-i+1).
代码:

#include <cstdio>
#include <iostream>
#include <cstring>
#include <algorithm>
using namespace std;
const int maxn= 101100;
//const double eps=1e-8;
int n;
double dp[maxn];
int main(){
    int T;
    int cas=0;
    //freopen("data.in","r",stdin);
    scanf("%d",&T);
    while(T--){
        dp[1]=1;
        scanf("%d",&n);
        for(int i=2;i<=n;i++){
            dp[i]=(dp[i-1]*(n-i+1)+n)/(double)(n-i+1);
        }
        printf("Case %d: %.10f\n",++cas,dp[n]);
    }

    return 0;
}

H题:
题意:岛上有n个老虎,m头鹿,一个人。老虎见了人或者鹿会吃掉,两只老虎相遇会互相吃掉。人与鹿见了结果取决于人。每天有且只有两只动物相遇,问人活下来的概率。
思路:由于不考虑天数,老虎遇见鹿什么的都是拖延时间,最关键的就是不让人和老虎相遇,也就是说必须老虎死光,所以不考虑鹿,只考虑人。对于每一天,由于是随机选取,所以方案数为c(n+1,2),人存活的方案数为c(n,2),所以概率为(n-1)/(n+1),每天老虎都会减少两只,遵循乘法法则。需要注意的是如果老虎为奇数那么无法避免人与老虎相遇的事情发生。
代码:

#include <cstdio>
#include <iostream>
#include <cstring>
#include <queue>
using namespace std;
int main(){
    int T,n,m,cas=0;
    double ans=1;
    //freopen("data.in","r",stdin);
    scanf("%d",&T);
    while(T--){
        scanf("%d%d",&n,&m);
        printf("Case %d: ",++cas);
        if(n&1){
            puts("0");
        }
        else {
            ans=1;
            while(n){
                ans*=(n*(n-1)/(double)(n*(n+1)));
                n-=2;
            }
            printf("%.10f\n",ans);
        }
    }
    return 0;
}

I题:
题意:给一个长为n的01串str,已知1的个数为a,将str前补一位1并去掉最后一位得到tp_str,求str和tp_str不匹配的位置的期望个数。
思路:dp[i][j][k]表示有i个字符,其中j个为1,并且以k结尾。
状态转移方程为
dp[i][j][1]=(1+dp[i-1][j-1][0])(i-j)/(i-1)+dp[i-1][j-1][1](j-1)/(i-1);
dp[i][j][0]=(1+dp[i-1][j][1])j/(i-1)+dp[i-1][j][0](i-1-j)/(i-1);
ans=dp[n][a][0]*(n-a)/n+dp[n][a][1]*a/n。
由于这个题又卡内存(不能n*n预处理)还卡时间,我用了滚动数组和一个min(i,a)的优化才勉强过了这个题。
代码:

#include <cstdio>
#include <iostream>
#include <cstring>
#include <algorithm>
using namespace std;
const int maxn=6000;
double f[2][2][maxn];
int main(){
    int T,n,m,cas=0;
    //freopen("data.in","r",stdin);
    scanf("%d",&T);
    while(T--){

        scanf("%d%d",&n,&m);
        int a=m-2*n;


        f[1][0][0]=1,f[1][1][1]=0;
        for(int i=2;i<=n;i++){
            f[i&1][0][0]=1;
            int ma=min(a,i);
            for(int j=1;j<=ma;j++){
                f[i&1][0][j]=((f[!(i&1)][1][j]+1)*(j)/(i-1)+f[!(i&1)][0][j]*(i-j-1)/(i-1));
                f[i&1][1][j]=((f[!(i&1)][0][j-1]+1)*(i-j)/(i-1)+f[!(i&1)][1][j-1]*(j-1)/(i-1));
            }

        }
        double ans=0;
        ans+=f[n&1][1][a]*(a)/n;
        ans+=f[n&1][0][a]*(n-a)/n;
        printf("Case %d: %.10f\n",++cas,ans);
    }
    return 0;
}

总结:刷了这么9道概率dp,好恶心,不过幸好聪神都会,以后可以开心的给聪神打辅助了。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值