我对数位dp的理解

dp即动态规划,一个很神奇的词,与其说是算法,不如认为是一种概念。
(有的时候你做完题,甚至都不知道自己用的是dp…)

我觉得吧,dp,就是把全局的最优解,拆分成部分最优解。
(也可能不是最优解,但与全局最优解有关)
一种牵一发而动全身的感觉。

数位dp,就是和数字排列有关,解决超长数字范围问题(比如范围到1e200000)。


思路

1、范围有多少位,就建多少个数组,每个数组代表该数位。
比如1e6 ,有6位,建8位就够了,dp[8]
dp[1]代表[1,9],dp[2]代表[10,99]

2、分解超长数字范围。
以分解dp[2]为例:
dp[2]即[10,99],我们需要知道上一个数位,即dp[1],[1,9];
dp[2]=[10,19]+[20,29]+[30,39]……;
[10,19],[20,29],[30,39],就是dp[1]分别加上前缀1、2、3;
所以分解dp[2],就是用dp[1]判断10次。
同理其他数位。

3、判断状态。
以dp[i][j][k]为例,表示符合 i 状态、j状态、k状态 的情况。
(初始化为-1)
第一个i 一般为数位,而后面的j、k状态则随着题目要求变化需要自行定义。


实现

用dfs递归每个数位来实现:

for(int i=0;i<=up/*代表数位*/;i++){
        if(/*条件*/){
           dfs(pos-1,/*下一状态*/,limit&&i==up);
         }
         else if(){
         }
        **注意是 else if 不是if**
        **血一样的教训**
 }

值得注意的是,递归数位时,我们需要知道我们的数位有没有到范围的上限:
比如 0~20时 :从0开始循环,最高为2(上限);
0开始时,接着从0~9开始(上限)。
我们可以发现上限有两种情况:

  1. 数位上的数字
  2. 9

如何判断:
(a[]记录数位)

int up=limit?a[pos]:9;

……
limit&&i==up

dfs结束后记录结果:

    if(!limit&&!lead){
        dp[pos][]=ans;
    }

每次递归时再剪个枝,重复状态不判断:

if(!limit&&dp[pos][]!=-1){
        return dp[pos][];
    }

dfs的意思是,递归给你的状态是还没被记录的状态,递归时确认上状态正确再递出去本次状态。如果状态不符合要求,本状态将被退回,不计入。

以 poj 3252 为例:

//求 0比1多的数
#include <iostream>
#include <string.h>
#include <stdio.h>
#define maxn 40

using namespace std;

int start,finish;
int dp[maxn][maxn][maxn];//i 数位 j 0的个数, k 1的个数
int a[maxn];

int dfs(int pos,int zero,int one,bool lead/*前导零*/,bool limit){
    if(pos<=0)return (lead||zero>one);
    if(!limit&&!lead&&dp[pos][zero][one]!=-1){
        return dp[pos][zero][one];
    }
    int up=limit?a[pos]:1;
    int ans=0;
    for(int i=0;i<=up;i++){
        if(!lead){//不是首位
            if(i){
                ans+=dfs(pos-1,zero,one+1,0,limit&&i==up);
            }
            else{
                ans+=dfs(pos-1,zero+1,one,0,limit&&i==up);
            }
        }
        else{//是首位
            if(i){//首位是1
                ans+=dfs(pos-1,0,0,0,limit&&i==up);
            }
            else{ //首位是0
                ans+=dfs(pos-1,0,0,1,limit&&i==up);
            }
        }
    }
    if(!limit&&!lead){
        dp[pos][zero][one]=ans;
    }
    return ans;
}

int solve(int n){
    int pos=0;
    while(n){
        if(n&1)a[++pos]=1;
        else a[++pos]=0;
        n>>=1;
    }
    return dfs(pos,0,0,1,1);
}

int main()
{
    while(~scanf("%d%d",&start,&finish)){
        memset(dp,-1,sizeof(dp));
        printf("%d\n",solve(finish)-solve(start-1));
    }
    return 0;
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值