hdu 3555 Bomb 2010 ACM-ICPC Multi-University Training Contest 12

来源:互联网 发布:网络商标注册费用 编辑:程序博客网 时间:2024/04/28 10:01

为了让智硬的自己以后还能看懂,尽量写详细点><

数位DP是按位考虑状态,然后再涉及到每一位数字的大小关系。

这里面用dp[i][0]表示i位数不包含49的个数,dp[i][1]表示i位数不包含49的但是以9开头的个数,dp[i][2]表示i位数包含49的个数,注意dp[i][0]包含了dp[i][1],最后枚举的时候不要算重了。

这里面的递推关系是从后i-1位推到后i位,新加的第i位的数位于高位。这里面的dp[i][j]是整个i位数所有数中有没有49,在下面的状态转移过程中还会根据input number每一位数的大小进行更新。

举个例子就可以找出递推关系:

_X49XX->XX49XX

_9XXXX->49XXXX

_XXXXX->9XXXX or XXXXX

因为新加的第i位可以填0-9共十个数,因此:

dp[i][0]=dp[i-1][0]*10-dp[i-1][1];//要减去49XXX的casedp[i][1]=dp[i-1][0];//直接在最高位填上9dp[i][2]=dp[i-1][2]*10+dp[i-1][1];//要加上49XXX的case

个人感觉如果从后高位往低位推,考虑最后一位是4的case,也是可行的?还木有试过。

后面的递推是从高位往低位推,智硬的我想了好久=。= 最后发现举个例子一目了然(⊙o⊙)…可见动手写一写还是比瞎想有作用,上次比赛要是多画几个sample那一题公式弄不好就弄出来了><

for example, n=5673

i=4:

0~4 [后面的数包含49]

4 [后面的数不包含49但是以9开头]

注意这里面最高位没有考虑5,因为dp[3][0 or 1 or 2]考虑的是0-999里面的数,所以这一位考虑5可能就会包含超出n的case

i=3:

0~5 [后面的数包含49]

54 [后面的数不包含49但是以9开头]

i=4的位置没有填5,所以i=3时在i=4处填的是5,因此不同的i对应的是disjoint sample space,所以最后的ans要加起来。

i=2:

5 60~6 [后面的数包含49]

5 64 [9XXXX]

i=1:

5 6 70~2 [XXX49XX]

由此可见,枚举的上界不会达到,最后枚举的只考虑了0~n-1的case,所以最初要n++。

然后要注意ans要设成long long,最后的数真心会很大(预处理的dp都出现了负的==)

#include<iostream>#include<stdio.h>#include<cstdio>#include<stdlib.h>#include<vector>#include<string>#include<cstring>#include<cmath>#include<algorithm>#include<stack>#include<queue>#include <ctype.h>using namespace std;int T;long long N;const int maxn=20;long long dp[maxn][3];int digit[maxn];long long ans;int len;void initial(){    memset(dp,0,sizeof(dp));    dp[0][0]=1;//0位数(null)不为49有一种方案,空方案作为一种方案    dp[0][1]=0;    dp[0][2]=0;    for(int i=1;i<maxn;i++)    {        dp[i][0]=dp[i-1][0]*10-dp[i-1][1];        dp[i][1]=dp[i-1][0];        dp[i][2]=dp[i-1][2]*10+dp[i-1][1];        //cout<<dp[i][0]<<" "<<dp[i][1]<<" "<<dp[i][2]<<endl;    }}void getdigit(){    len=0;    memset(digit,0,sizeof(digit));    N++;    memset(digit,0,sizeof(digit));    while(N>0)    {        digit[++len]=N%10;        N/=10;    }}void DP(){    ans=0;//为什么ANS要加起来?    bool flg=false;    int last=0;    for(int i=len;i>=1;i--)    {        ans+=dp[i-1][2]*digit[i];//如果后面的数包含49,这一位填什么数都无所谓,只要比N小       // cout<<" 1 "<<i<<" "<<ans<<endl;        if(flg)//如果高位出现过49(不一定是紧邻的前两位),后面可以填没有49的数        {            ans+=dp[i-1][0]*digit[i];            //flg=false;         //   cout<<" 2 "<<i<<" "<<ans<<endl;        }        if(!flg&&digit[i]>4)// 如果前面没有49,第i位可以填4,后面接以9开头的数。        {//并且必须!flg才可以执行,因为dp[i][0]包含了dp[i][1]的case,所以前面出现过49,上一步已经包含了第i位填4,后面接以9开头的数的case。            ans+=dp[i-1][1];        //    cout<<" 3 "<<i<<" "<<ans<<endl;        }        if(last==4&&digit[i]==9)        {            flg=true;        }        last=digit[i];    }    printf("%I64d\n",ans);}int main(){    freopen("input.txt","r",stdin);    // freopen("data.txt","r",stdin);     //freopen("out1.txt","w",stdout);     scanf("%d",&T);     initial();     for(int i=0;i<T;i++)     {         scanf("%I64d",&N);         getdigit();         DP();     }     return 0;}



        

0 0