hdu 3555 Bomb 按位dp模板题

我在这个博客上看的 http://www.cnblogs.com/ronaflx/archive/2011/04/18/2020141.html#2309504
然后又问了下 ronaflx 大牛,给了我一种新的方法和代码 且 和我的想法又像就记录下。
按位dp看过很多遍,期间fatboy ZaviOr 也讲过但始终觉得代码不舒服就放一边没认真学。这种格式总算习惯了,简洁又好想。
-----------------------------------------------------------------------------------------------------------------
题目求 1 - N 中含49的个数。 学过ac自动机的可能立即就想到:设三种状态
/*
 * 0 不含49且以非4的数字结尾
 * 1 不含49且以4这个数字结尾
 * 2 含49的状态
 */
这三种状态可以相互转化:
dp[k][0] = dp[k - 1][1] * 8 + dp[k - 1][0] * 9;
dp[k][1] = dp[k - 1][0]  + dp[k - 1][1];
dp[k][2] = dp[k - 1][2] * 10 + dp[k - 1][1];
再把整数N看成字符串,就可以从左到右dp过去。 这就是按位dp了。
但没那么简单,例如 N = 12345
考察第一位:如果取 0 ,第二位可以取遍0-9所有数(即后面的可以按自动机那个递推关系直接算出来); 如果取 1 ,第二位最大取2了。
所以,某一位在DP的过程中往右转移,必须分两种情况:到该位的前缀小于上界(如第三位时为122 或109等等),可以直接用自动机方法转移;前缀刚好等于上界(如到第三位时为123),右边一位就不能取遍 0 - 9 的所有数,要DP一下了。
所以,设个三维数组: dp[ 1 - len ] [ 0 1 2 ] [ 0 1 ]     第一维表示字符串从左到右的位置,  第二维表示自动机的三种状态, 第三维表示当前位上的数字是否等于上界(即 从左到该位的前缀都相等。如到第三位时为123**)

************************************************************************************************************************************

#include<iostream>
#include<cstdio>
#include<cstdlib>
#include<cstring>
#include<memory.h>
#include<time.h>
using namespace std;
typedef __int64 LL;
const int N = 22;
char s[N];
LL dp[N][3][2];

/*
 * 0 不含49,末尾不是4
 * 1 不含49,末尾是4
 * 2 含49
 * 0 没到上界; 1 到上界
 */

int next(int now,int bit){                                                        //状态转移,当前是 now状态,后面加一位数 bit 得到的新状态
    if(now==2 || now==1&&bit==9) return 2;
    return bit == 4;
}

LL DP(char *s,int len){
    memset(dp,0,sizeof(dp));
    dp[0][0][1] = 1;
    for(int i=1;i<=len;++i){                             //字符串

        //from 0 to 0
        dp[i][0][0] = dp[i-1][0][0]*9 + dp[i-1][1][0]*8;
        dp[i][1][0] = dp[i-1][0][0] + dp[i-1][1][0];
        dp[i][2][0] = dp[i-1][2][0] * 10 + dp[i-1][1][0];

        //from 1 to 0
        for(int j=0;j<s[i]-'0';++j){
            dp[i][next(0,j)][0] += dp[i-1][0][1];
            dp[i][next(1,j)][0] += dp[i-1][1][1];
            dp[i][next(2,j)][0] += dp[i-1][2][1];
        }

        //from 1 to 1
        dp[i][next(0,s[i]-'0')][1] += dp[i-1][0][1];
        dp[i][next(1,s[i]-'0')][1] += dp[i-1][1][1];
        dp[i][next(2,s[i]-'0')][1] += dp[i-1][2][1];
    }
    return dp[len][2][0] + dp[len][2][1];
}

int main(){
    int t;
    scanf("%d",&t);
    while(t--){
        scanf("%s",s+1);
        printf("%I64d\n",DP(s,strlen(s+1)));
    }
    return 0;
}


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值