Uva10328 dp(递推+高精度)

 

 

 

题目链接:http://vjudge.net/contest/136499#problem/F

题意:给你一个硬币,抛掷n次,问出现连续至少k个正面向上的情况有多少种。

 一个比较好理解的题解:原题中问出现连续至少k个H的情况,很难下手。我们可以试着将问题转化一下,设dp[i][j]表示抛掷i个硬币出现连续至多j个H的情况种数。

实际上原题中的出现连续至少k个H,即出现连续k个H,k+1个H,...n个H的并集,等价于dp[n][n]-dp[n][k-1],即从连续至多n个H的情况(其实这就是所有的抛掷情况种数)减去连续至多(k-1)个H的情况,这保证得到的所有情况一定至少有k个连续的H。

现在问题就变成了怎么求dp[i][j]。

考虑当i<=j的时候,dp[i][j]=dp[i-1][j]*2,即从上一阶段得到的抛掷序列后面增加正和反两种情况,如果出现连续的H个数大于j个,这种情况是非法的,但很显然此时不会出现这种情况。

当i>j时,如果继续用dp[i][j]=dp[i-1][j]*2就不行了。因为如果 从i-j到第i-1全部都是H ,那么这时候在第i个位置再加一个H,就会出现连续的H个数大于j个的非法状态,所以我们需要减掉 从i-j到第i-1全部都是H 的这种情况。那么这种情况有多少种呢。我们考虑该状态是如何转移而来的。试想第i-j-1个位置应该是什么呢。很明显应该是F。如果是H那就会出现非法状态了。那在第i-j-1之前的位置呢。无论H和F都可以,只要不出现连续的H个数大于j的非法状态即可,这就是dp[i-j-2][j]。

那么这样,dp[i][j]=dp[i-1][j]*2-dp[i-j-2][j]。

但这还是不够的。我们之前的推导都是基于第i-j-1个位置一定存在的前提下(i>j不能保证第i-j-1个位置一定存在),那如果第i-j-1个位置不存在,第i-j-2个位置也就不存在,上述方程也就不成立了。但这种情况很好想,此时一定是i==j+1,从第1个位置到第j个位置全部都是H,只有这一种情况,所以方程变成dp[i][j]=dp[i-1][j]*2-1。

综上:

dp[i][j]表示抛掷i个硬币出现连续至多j个H的情况种数

dp[0][j]=1

i<=j: dp[i][j]=dp[i-1][j]*2;

i>j :if(i==j+1): dp[i][j]=dp[i-1][j]*2-1;

      else: dp[i][j]=dp[i-1][j]*2-dp[i-j-2][j]

ans=dp[n][n]-dp[n][k-1]

需要用到大数。

剩下的就是写代码了,一个高精度乘法,一个高精度减法。

//没办法java还没学 ==

 

AC代码:

#include<cstdio>
#include<cstring>
using namespace std;

const int N=100+10;

void mulsn(char *b,char *a,int k) //乘法 b=a*k
{
    int i,l=strlen(a),s=0;
    for(i=0; i<l || s; i++)
    {
        if(i<l) s+=(a[i]-'0')*k;
        b[i]=s%10+'0';
        s/=10;
    }
    b[i]=0;  //  '/0'
}

void minuss(char *a,char *b) //减法 a-b
{
    int i,j,k,la=strlen(a),lb=strlen(b);
    for(i=0,j=0,k=0; i<la || j<lb || k; i++,j++)
    {
        if(i<la) k+=a[i]-'0';
        if(i<lb) k-=b[j]-'0';
        if(k<0) k+=10, a[i+1]-=1;
        a[i]=(k%10)+'0';
        k/=10;
    }
    while(i>1 && a[i-1]=='0') i--;
    a[i]=0;
}

char sum[N][N][N];
char ch[100]="1";

int main()
{

    int n,k;
    while(scanf("%d%d",&n,&k)==2)
    {
        for(int i=0; i<=n; i++)
        {
            sum[0][i][0]='1';
            sum[0][i][1]=0;
        }
        for(int i=1; i<=n; i++)
        {
            for(int j=0; j<=n; j++)
            {
                mulsn(sum[i][j],sum[i-1][j],2);
                if(i==j+1) minuss(sum[i][j],ch);
                else if(i>j) minuss(sum[i][j],sum[i-j-2][j]);
            }
        }
        minuss(sum[n][n],sum[n][k-1]);
        int l=strlen(sum[n][n]);
        for(int i=l-1; i>=0; i--)
            printf("%c",sum[n][n][i]);
        puts("");
    }
    return 0;
}

 

转载于:https://www.cnblogs.com/a-clown/p/5971164.html

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值