zju Back to the Past 4624

//题目链接:http://acm.zju.edu.cn/onlinejudge/showProblem.do?problemId=4624
/*
    题目来源:浙大十周年校赛第B题

    题目类型:DP求期望值
    
    结题报告人:SpringWater(GHQ)
    

    题目大意:一个月光宝盒有两个面,每个面有n个洞,每个洞在晚上到来的时候就可能吸收月光的
                能量而发亮,每个洞亮的概率为P,当两边同时都至少有m个洞亮时盒子就可以工作了!
                问;当盒子工作时要经历的天数的期望值!

    解题思路:首先考录到天数可能是无限,所以不可能算出每种天数再乘上该天数的概率!所以应考虑
                将无限的期望值转换为有限的状态方程;

                ps:下面这个状态方程虽然是绝对本人的原创,没借鉴任何解题报告,但是在之前我已经看了
                其他很道题(如:成都网络赛的Maze即杭电4035)用DP求期望值类型的题,所以再回头做
                这题的时候就感觉just a cake!所以高难度算法题,还是的事先多练,才能在比赛中AC,否则
                如果你想在比赛中现成的想出这等法来ac它,那可能是一个比泡到凤姐都更遥远的梦!!

                令:E[i][j]为当前左右面分别已经亮了i,j个洞,E[0][0]为所求!
                E[i][j]=∑p(i+m,j+n)(E[i+m][j+n]+1)  (E[i][j]为当前已经左边有i个洞已经亮了,同时右
                边j个洞已经亮了的前提下需要天数的期望值,p(i+m,j+n)是指从当前E[i][j]跳转到E[i+m][j+n]
                的概率且p(i+m,j+n)=C[N-i][m]*PPow[m]*NPPow[N-i-m]*C[N-j][n])*PPow[n]*NPPow[N-j-n])又
                因为E[i][j]==0(当i>=m&&j>=m时)所以当前E[0][0]可由前面的退出来,只用单独考虑一下
                (m=0&&n=0)时的情况对于每一个E[i][j]可得到E[i][j]=a*E[i][j]+b;直接解这个一元一次方程就可!

    回顾此题感悟:1此类题关键是利用假设前面的(E[i+m][j+n]已经算出来的前提下,将(E[i][j]的状态转移到(E[i+m][j+n]
                    上即可解决无限次带来的误差与繁琐!
                
                  2.做完这题要是在增加:规定当两面分别有确定的x,y个亮时,则原来亮的全部熄灭从新开始,那这个
    题的难度又可以增加了!(有兴趣的大牛们不妨思考思考!)其实要是你对此类题比较熟悉的话,一样的可以解决,
    因为这题的解法就和成都区域赛的那个题型就很类似了!

*/


#include<stdio.h>
#include<math.h>
#include<iostream>
#include<string.h>
using namespace std;
double C[65][65];
double PPow[210],NPPow[210];
int inite()            //预处理i个洞里面选j个的选择种数C[i][j]因为只有50*50*25的
{                    //复杂度不会超时,果断预处理!以备等会求概率C[N-i][m]*PPow[m+n]*NPPow[N-i-m+N-j-n]*C[N-j][n])使用
    int i,j,k,m,top;
    for(i=0;i<=60;i++)
    {
        top=i/2;
        for(j=0;j<=60;j++)
        {
            if(j)
            {
                if(j<=i)
                {
                    if(j>top)
                        C[i][j]=C[i][i-j];
                    else
                    {
                        C[i][j]=1;
                        for(k=1,m=i;k<=j;k++,m--)
                        {
                            C[i][j]*=(double)m;
                            C[i][j]/=(double)k;
                        }
                    }
                }
                else
                    break;
            }
            else
                C[i][j]=1;
        }
    }
    return 0;
}
int deal(double p,int n)//预处理p^x和(1-p)^x以备等会求概率C[N-i][m]*PPow[m+n]*NPPow[N-i-m+N-j-n]*C[N-j][n])使用
{
    int i;
    for(i=0;i<=n;i++)
        PPow[i]=pow(p,(double)i),NPPow[i]=pow((1-p),(double)i);
    return 0;
}
int visit[60][60];
int main()
{
    int N,M;
    double E[65][65],P,num;
    int i,j,m,n;
    freopen("input.txt","r",stdin);
    inite();
    while(scanf("%d%d%lf",&N,&M,&P)&&(N||M))
    {
        if(fabs(1-P)<1e-20)//因为考虑到m<=n,当p=1是那就只用一个晚上就可达到目标
            printf("1.000000\n");
        else
        {
            deal(P,2*N);//预处理
            for(i=0;i<=60;i++)
            {
                for(j=0;j<=60;j++)
                    E[i][j]=0;
            }
            memset(visit,0,sizeof(visit));
            for(i=N;i>=M;i--)
                for(j=N;j>=M;j--)
                    visit[i][j]=1;//将已经达到宝盒工作的状态排除,以为她们的期望值一定为0,故不用再求
            for(i=N;i>=0;i--)
            {
                for(j=N;j>=0;j--)
                {
                    if(!visit[i][j])//承接上面:将已经达到宝盒工作的状态排除,以为她们的期望值一定为0,故不用再求
                    {
                        num=0;
                        for(m=0;m<=N-i;m++)
                        {
                            for(n=0;n<=N-j;n++)//从E[i][j]跳转到E[i+m][j+n]+1),前提(i+m《=n&&j+n《=n)
                            {
                                if((m==0)&&(n==0))//当状态转移到自己身上时,不能加上p(i,j)*E[i][j]而应化到左边去,如:求x(x=a*x+b)
                                    num+=NPPow[2*N-i-j];
                                else
                                    //将除自己以外的所有状态采取期望加权的方式转移
                                    num+=(C[N-i][m]*PPow[m+n]*NPPow[N-i-m+N-j-n]*(E[i+m][j+n]+1)*C[N-j][n]);
                            }
                        }
                        E[i][j]=num/(1-NPPow[2*N-i-j]);
                    }
                }
            }
            printf("%0.6lf\n",E[0][0]);
        }
    }
    return 0;
}


/*
顺便附上:今天比赛的解题代码:
账号:SpringWater

A题
#include<stdio.h>
int main()
{
    double x,sum;
    int num;
    while(scanf("%lf",&x)!=EOF)
    {
        num=int(x);
        if(num%2)
        {
            num/=2;
            sum=(double)(num+1)*(double)num;
            sum+=(num+1)*(x-double((int)x));
        }
        else
        {
            num/=2;
            sum=(double)(num)*(double)(num-1);
            sum+=(double)num;
            sum+=(double)num*(x-double((int)x));
        }
        printf("%0.2lf\n",sum);
    }
    return 0;
}

D题:
#include<stdio.h>
#include<string.h>
int main(void)
{
    char ch,s[1500];
    int num,i,mark;
    freopen("input.txt","r",stdin);
    while(scanf("%c",&ch)!=EOF)
    {
        if(ch=='\n')
            num=0;
        else
        {
            s[0]=ch;
            scanf("%s%c",&s[1],&ch);
            num=strlen(s);
        }
        if(num)
        {
            for(i=0;i<num;i++)
            {
                if(s[i]=='+'||s[i]=='-')
                {
                    if(!(s[i+1]=='('||((s[i+1]>='0')&&(s[i+1]<='9'))))
                        break;
                }
                if(s[i]=='/'||s[i]=='*')
                {
                    if(!i||!((s[i+1]=='(')||((s[i+1]>='0')&&(s[i+1]<='9'))))
                        break;
                }
                if(s[i]=='(')
                {
                    if(!(s[i+1]=='+'||s[i+1]=='('||s[i+1]=='-'||((s[i+1]>='0')&&(s[i+1]<='9'))))
                        break;
                }
                if(s[i]==')')
                {
                    if(!(s[i+1]=='\0'||s[i+1]==')'||s[i+1]=='*'||s[i+1]=='/'||s[i+1]=='+'||s[i+1]=='-'))
                        break;
                }
                if((s[i]>='0')&&(s[i]<='9'))
                {
                    if(s[i+1]=='(')
                        break;
                }
            }
            if(i<num)
                printf("No\n");
            else
            {
                mark=0;
                for(i=0;i<num;i++)
                {
                    if(s[i]=='(')
                        mark++;
                    if(s[i]==')')
                        mark--;
                    if(mark<0)
                        break;
                }
                if(mark!=0)
                    printf("No\n");
                else
                    printf("Yes\n");
            }
        }
        else
            printf("\n");
    }
    return 0;
    
}
*/


评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值