数位DP

数位DP

概念

数位DP就是一种用于计数的dp, 一般用于统计区间[l, r]中符合条件的数的个数。所谓数位dp,就是在各个数位上进行dp。之所以引入dp的概念,其实是为了进行统计。数位dp的实质其实就是一种暴力枚举,但这种枚举方式满足dp的性质,然后进行记忆化搜索就行了。

而这种枚举大概有两种方式,第一种:

for (int i = l; i <= r; ++ i)
    if (check(i)
	Ans ++;

新的枚举:控制上界枚举,从最高位开始往下枚举,例如:r = 213,那么我们从百位开始枚举:百位可能的情况有0, 1, 2 (0 等会会处理)

然后每一位枚举都不能让枚举的这个数超过上界213(下界就是0或者1,这个次要),当百位枚举了1,那么十位枚举就是从0到9,因为百位1已经比上界2小了,后面数位枚举什么都不可能超过上界。所以问题就在于:当高位枚举刚好达到上界是,那么紧接着的一位枚举就有上界限制了。具体的这里如果百位枚举了2,那么十位的枚举情况就是 0 ~ 1 ,如果前两位枚举了 2, 1,最后一位之是 0 ~ 3 (这一点正好对于代码模板里的一个变量 limit 专门用来判断枚举范围)。最后一个问题: 最高位枚 0, 百位枚举 0,相当于此时我枚举的这个数最多是两位数,如果十位继续枚举0,那么我枚举的就是以为数咯,因为我们要枚举的是小于等于 r 的所有数,当然不能少了位数比 r小的。(这样枚举是为了无遗漏的枚举,不过可能会带来一个问题,就是前导零的问题,模板里用 lead 变量表示,不过这个不是每个题目都是会有影响的,可能前导零不会影响我们计数,具体要看题目)

由于这种新的枚举只控制了上界所以我们的主函数大约是这样:

int l, r;
scanf("%d %d", &l, &r);
printf("%d", solve( r ) - solve(l - 1));

Emmm,话是这么说,其实题目的情况很多,这里给出一种模板:

dfs(数的最后若干位, 限制条件, 当前第几位)
    if (最后一位)
        return (各种限制条件下的返回值);
    局部变量 now = 当前位的数字
    局部变量 sum = 0;
    for (i = 0; i <= now - 1; ++ i)
    {
	sum += (当前位取 i 时一定无限制的合法状态数)
        sum += (当前位取 i 时满足当前限制的合法状态数)
    }
    根据 now 更新限制条件 不再满足则 return sum;
    return sum + dfs(当前位后的若干位, 更新后的限制条件, 下一位);

solve( 当前数 )
    if ( 只有一位 )
    	return 对应的贡献;
    局部变量 now;
    for (now=可能最高位; now >= 1; -- now)
        if (当前位有数字)
            break
    局部变量 hav = 当前位数字
    局部变量 sum = 0
    for (i = 1 ; i < hav; ++ i)
        sum += 当前位取 i 后合法情况任意取的贡献
    for (i = 1 ; i < now; ++ i)
        for (j = 1; j <= 9; ++ j)
            sum += 第 i 位取 j 后合法情况任意取的贡献
    sum += dfs(去掉第一位后的若干位, 限制条件, 第二位)
    return sum;

main()
    预处理当前位取 i 的各种条件各种限制的贡献
    read( L ); read( R );
    cout << solve(R) - slv(L - 1);
    return 0

这个模板能解决大多数的数位dp问题,其他情况还要再讨论。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值