让我们来写数位DP吧~

HDU 2089 不要62

限制:
62 不能连号, 不能出现4

思路:
1. 对于当前位数字是 4 有限制,可以直接处理!
2.
pre_num(前一个数字) 有限制;
if(pre_num == 6) dp当前位不能取 2 状态变化.
else dp数组可以任意取.
所以对于当前位 的 前面位有两种情况,
但是我们是写的DFS,所以先对于后面的状态先更新,所以dp更新的时候对于后一位要记录dp[][0/1] (0/1代表 pre_num(=6 / !=6 ))

int dp[10][2], a[10];
int DFS(int pos, int sta, bool limit){
    if(pos < 0) return 1;
    if(!limit && dp[pos][sta] != -1) return dp[pos][sta];
    int up = limit ? a[pos] : 9;
    int temp = 0;
    for(int i=0;i<=up;i++){
        if(i == 4) continue;
        if(sta && i == 2) continue;
        temp = temp + DFS(pos-1, i==6, limit && (i == up));
    }
    if(!limit) dp[pos][sta] = temp;
    return temp;
}

int solve(int n){
    int num = 0;
    while(n){
        a[num++] = n % 10;
        n /= 10;
    }
    return DFS(num-1, 0, true);
}

int main(){
    int n, m;
    memset(dp, -1, sizeof(dp));
    while(scanf("%d%d", &n, &m)){
        if(!n && !m) break;
        printf("%d\n", solve(m) - solve(n-1));
    }
    return 0;
}

HDU 4734 F(x);

在区间[0, B]内有多少个 F(x) <= F(A).

F(x) = An * 2n-1 + An-1 * 2n-2 + … + A2 * 2 + A1 * 1.

限制:
1. 先考虑F(x) 的最大值,最高(2^9 - 1) * 9 = 525 * 9

数位DP(大)都是从高位搜下去的,如果按照题意考虑我们持续从最高位搞个中间值加起来!
dp可以记录,当前位是多大的 个数。
但是如何转移呢???对于之前那位,OK,我已经知道之前的和 pre_sum 是多少,
现在我们只要算一下当前pos 满足 F(A) - pre_sum >= 0的那些 DP 值.
考虑减法;
如果对于当前值就是F(A),那么之后的权值和是不是<=F(A)就行了,
是不是变成了当前值是temp,那么之后的权值和是不是<=temp就行了,
那我直接把temp直接记录,就是对于当前位 <= temp 的数量.

注意:
存数位的数组一些位置无意义但被使用,要初始化!

int dp[11][5000];
int a[11];
int n, m;

int DFS(int pos, int sta, bool limit){
    if(sta < 0) return 0;
    if(pos < 0) return 1;
    if(!limit && dp[pos][sta] != -1) return dp[pos][sta];

    int up = limit ? a[pos] : 9;
    int ans = 0;
    for(int i=0;i<=up;i++)
        ans = ans + DFS(pos-1, sta-(1<<pos)*i, limit && (i == up));
    if(!limit) dp[pos][sta] = ans;
    return ans;
}

int solve(){
    int num = 0;
    int temp = 0;
    while(n){
        temp = temp + (n % 10) * (1<<num);
        n /= 10;
        num++;
    }
    memset(a, 0, sizeof(a));
    num = 0;
    while(m)
    {
        a[num++] = m % 10;
        m /= 10;
    }
    return DFS(num-1, temp, true);
}

int main(){
    int T, cas=1;
    scanf("%d", &T);
    memset(dp, -1, sizeof(dp));
    while(T--){
        scanf("%d%d", &n, &m);
        printf("Case #%d: %d\n", cas++, solve());
    }
    return 0;
}

51nod1009 数字1的数量

1-N的数的1的数量;

数位上的思考:
1. 个位上1的数量,不考虑个位限制,也就是意味着区间[1, 9],num0 = 1;
2. 加一个十位 1的数量,不考虑个/十位限制,也就是意味着区间[1, 99],
num1 = (两位数, 1 * 10(十位为1,个位随意[0, 9]) + 1 * 9(个位为1, 十位(1, 9))) + 1(个位数, num0 = 1)
3. 再加一个百位 1的数量,不考虑个/十/百位限制,也就是意味着区间[1, 999]
num2 = (三位数, 1 * 100(百位为1,其余随意[0, 99] + 1 * 90(十位为1, 个位[0, 9], 百位[1, 9]))) + (两位数, num1)
4. 现在直接改称 <= 4 位的一个数[1, 9999],
num4 = 1 * 1000 + 1 * 9 * 100 + 1 * 9 * 100 + 1 * 9 * 100 + num2 = 1*1000 + 3*900 + num2.

显然这还是不够的,
我们还要考虑,
like 区间[1, 36], 显然[1, 9],我们现在要知道的是[10, 36], 但是3>=1, 还是没有限制
like 区间[1, 106], 其实显然[1, 99]我们都知道了, 我们现在要知道的是[100, 106], 百位==1, 总共有6个, 十位只能是0, 个位1的时候,只能是百位为1十位是0
like 区间[1, 236], 其实显然[1, 99]我们都知道了, 我们现在要知道的是[100, 236], 百位==1, 总共有37[0, 36], 个位为1,百位可以 2 1/2, 个位可以[0, 9]
like 区间[1, 216], 其实显然[1, 99]我们都知道了, 我们现在要知道的是[100, 216], 百位==1, 总共有17[0, 16], 十位为1,百位为1, 个位可以10[0, 9], 但是百位为1, 个位只能是7[0, 6]
like 区间[1, 2345], 其实显然[1, 999]我们都知道了, 我们现在要知道[1000, 2345], 千位==1, 总共有346[0, 345],
百位为 1, 千位可以1/2, 后面就是 46 [0, 45]
总结一下???
对于这里我们可以考虑特殊情况然后暴力可以得到。
感觉方法太烂!!!
考虑递推,我们从低到高处理,
答案是累加的过程。
like 区间[1, 36]至36, 对于最高位是1, 都OK就是加[0, 9] 然后考虑尾数1的个数(统筹考虑), 那么就是 +=num1*3([0,1,2]), 之前的就是高位为3的情况
like 区间[1, 106]至106, 对于最高位是1, 那么就是 +=7[0, 06](+尾数+1), 然后加上num2*1(最高位取0)
like 区间[1, 236]至236, 对于最高位是1, 都OK就是加[0, 99], 然后考虑尾数1的个数, 那么就是 +=num2*3([0,1,2]) + 之前的就是高位为3的情况
OK!

也可以考虑DFS,考虑每一位的数的贡献。


CodeForces55D Beautiful numbers

题意:
查询区间有多少个 整除 自己每位上非0的数

限制:
0无限制、能整除每一位。
X % Y == 0
X * 10 % Y == 0

方案1.[GG]
一开始开了临时值然后直接dp[pos]巨撒比,明明这一位对于前一位是有特殊关系的,然后直接后一位求出来了,对于前一位的另一个都不满足了
like -> if(!limit && dp[pos] != -1) return dp[pos];
死于–>转移

方案2.[GG]
想了开状态来记录 [至当前] 能 (%x == 0) 的方案数,但是没用啊!!!
但是中间写崩了————对于中间的时候,很撒比地 如果 [(中间值*10 + i % i) != 0] 直接不管了!!
woc??? 你怎么知道的??? 对于中间的时候 虽然现在不能模i等于0,不代表之后加一些数可以实现膜i等于0吧
死于—>转移

总结一下————
1.显然我们是需要中间值的,用来转移
等等!这个中间好大啊!怎么优化一下,%(123456789的LCM) 就好了嘛.
2.我们无法在中间判断这个值是不是不合法!!!NO WAY!!!所以我们还要记录到个位的时候是不是对所有的位都能膜他等于0
Sooooooooo!!!我们是不是要记录前面这些位,怎么记录————乘起来很棒棒!!!————LCM更加棒棒!!!
直接存 空间复杂度好像很大!!!我们考虑LCM有多少个?(话说怎么求啊)这些LCM (1*2*3*…*8*9) 的约数嘛, 我们可以离散化一下

方案3.
现在我们数位DP, 每次会记录一个中间值temp,一个lcm, 不满足的话 就是到了最低位 temp % lcm != 0;
那么我们开 dp[][][]三维就可以刚了!

int mod, hs[3000], xs[55];
void init(){
    mod = 2520;
    int num = 0;
    for(int i=1;i<=mod;i++)
        if(mod % i == 0){
            xs[++num] = i;
            hs[i] = num;
        }
//    printf("%d\n", num);
}

int a[25];
LL dp[25][3000][55];
int LCM(int x, int y){
    return x * y / __gcd(x, y);
}

LL DFS(int pos, int temp, int lcm, bool limit){
    if(pos < 0){
        if(temp % lcm == 0) return 1;
        return 0;
    }
    if(!limit && dp[pos][temp][hs[lcm]] != -1) return dp[pos][temp][hs[lcm]];
    int up = limit ? a[pos] : 9;
    int tmp, tlcm;
    LL ans = 0;
    for(int i=0;i<=up;i++){
        tmp = (temp * 10 + i) % mod;
        if(i){
            tlcm = LCM(lcm, i);
            ans = ans + DFS(pos-1, tmp, tlcm, limit && (i == up));
        }
        else ans = ans + DFS(pos-1, tmp, lcm, limit && (i == up));
    }
    if(!limit) dp[pos][temp][hs[lcm]] = ans;
    return ans;
}

LL solve(LL n){
    int num = 0;
    mem(a, 0);
    while(n){
        a[num++] = n % 10;
        n /= 10;
    }
    return DFS(num-1, 0, 1, true);
}

int main(){
    int T;
    LL l, r;
    init();
    mem(dp, -1);
    scanf("%d", &T);
    while(T--){
        scanf("%lld%lld",&l, &r);
        printf("%lld\n", solve(r) - solve(l-1));
    }
    return 0;
}

HDU3555 Bomb

题意:
1-N到有多少个数是包含”49”的.

思路:
还记得不要62嘛…
emmmmm, 我们算一下不要49…然后哼哼哼…

LL dp[25][2];
int a[25];

LL DFS(int pos, int sta, bool limit){
    if(pos < 0) return 1;
    if(!limit && dp[pos][sta] != -1) return dp[pos][sta];
    int up = limit ? a[pos] : 9;
    LL ans = 0, t;
    for(int i=0;i<=up;i++){
        if(sta && i == 9) continue;
        if(i == 4) t = 1;
        else t = 0;
        ans = ans + DFS(pos-1, t, limit && (i == up));
    }
    if(!limit) dp[pos][sta] = ans;
    return ans;
}

LL solve(LL n){
    int num = 0;
    mem(a, 0);
    while(n){
        a[num++] = n % 10;
        n /= 10;
    }
    return DFS(num-1, 0, true);
}

int main(){
    int T;
    LL n;
    mem(dp, -1);
    scanf("%d", &T);
    while(T--){
        scanf("%lld", &n);
        printf("%lld\n", n - solve(n) + 1);
    }
    return 0;
}

LightOJ 1140

题意:
给出一个区间,求区间内0出现的个数。
思路:
因为窝比较蠢,在想这个特殊位不就是0嘛,ok,状态设个 是0/不是0,感觉美滋滋。
但是显然,这就有一个重大的问题,是的没错,对于当前位,123456789这些数字,他的后多少位出现0的个数都没区别,
但是数位DP,这个DP记录的值表示的是对于当前位的前面所有情况!!!所以太明显了这个思路错的!
所以DP的状态 需要的是到当前位的一类情况
那么对于这道题,记录从首位到当前位零的个数就对了。


LL dp[25][15];
int a[25];

LL DFS(int pos, bool pre_zero, bool limit, int sta){
    if(pos < 0) return (pre_zero ? 1 : (LL)sta);

    if(!limit && !pre_zero && dp[pos][sta]!=-1) return dp[pos][sta];
    int up = limit ? a[pos] : 9;
    LL temp = 0;
    for(int i=0;i<=up;i++){
        if(pre_zero){
            temp += DFS(pos-1, pre_zero&&i==0, limit&&i==up, sta);
        }
        else{
            if(!i) temp += DFS(pos-1, pre_zero, limit && i==up, sta+1);
            else temp += DFS(pos-1, pre_zero, limit && i==up, sta);
        }
    }
    if(!limit && !pre_zero) dp[pos][sta] = temp;
    return temp;
}

LL solve(LL x){
    if(x<0) return 0;
    if(x==0) return 1;
    int num = 0;
    while(x){
        a[num++] = x % 10;
        x /= 10;
    }
    return DFS(num-1, true, true, 0);
}

int main(){
    LL n, m;
    int T, cas = 1;
    memset(dp, -1, sizeof(dp));
    scanf("%d", &T);
    while(T--){
        scanf("%lld%lld", &n, &m);
        printf("Case %d: ", cas++);
        printf("%lld\n", solve(m) - solve(n-1));
    }
    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值