hdu 5898 odd even number(acm/icpc沈阳赛区网络赛,数位DP)

传送门: http://acm.hdu.edu.cn/showproblem.php?pid=5898

题目大意:
定义odd-even-number是一个数,其中连续奇数位的长度是偶数,连续偶数位的长度是奇数。
问[L,R]里有几个odd-even number?

题目分析:
其实呢我知道数位DP是干什么用的,但是没见过相关的题,所以即使一眼就看出用数位dp做,但却看了一天题解才弄懂这道题。

其实一开始我理解错题意了,以为是只要存在连续奇数位长度为偶数、连续偶数位长度为奇数就可以,但跑了一下别人的ac代码,发现并不是。
因为11222是odd-even-number,112221就不是,但1122233就是。
所以其实题意应该是,所有连续奇数位的长度都是偶数(包括0),所有连续位偶数的长度都是奇数。

这里用dp[i][j][k]表示,pos位,前一位是j,j那位前面有k个连奇或连偶的数的odd-even-number数。因为每次累加的时候都必须是满足pos往前k位满足连奇或连偶才加进去的,而pos+k位以上的又已经确保符合定义,所以是与当前子问题无关的。因此满足DP性质,go!

这里有两个要注意的,就是在搜索解的时候,要注意前导0,即假如要找1~12345范围内的解,我们是要从00001 搜索到 12345。而前导0对奇偶性是不影响的(即不算入连续偶数),第二个要注意的是搜索到第pos位的时候,该位的取值范围未必是0-9,是否有限制受高位影响(如果高位有限制,且当前数和上限的对应位相等)。

那么关键的问题来了,你DP总要有个转移方程啊,转移方程是什么呢?

我们记up代表第i位上的取值上限(可能是9,也可能是输入的那个上限r的对应位),那么有:

dp[i][j][k]=s=1updp[i1][s][k+1/1]

第三个下标取1还是k+1根据j和k的奇偶性判断。如果j奇k偶,或者j偶k奇,那么如果s跟j同奇偶性,就取k+1,否则取1(因为这时候前面的k位已经确保是解了,奇偶性变了自然从1再开始算),如果j、k奇偶性相同,那么只有s跟j同奇偶性才是解,而且取k+1,(例如前面是112222,如果再放一个1往下搜,就是不符合定义的)

然后编码的时候记住从高往低,深度优先搜索,因为位数是从pos-1到0的,所以从pos-1开始搜,搜到-1的时候说明前面都搜完了,只要看最后一轮连续数是否符合定义就好。

这题看了大半天,好。。。菜。。。啊。。。

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
int t;
ll l,r;
ll dp[20][20][20];
int bit[20];
ll dfs(int pos,int j,int k,bool lead,bool limit) {
    //pos位开始,上一位是j,连续的奇数或者偶数有k,有没有前导0,第pos位是否有上界(即如果求1~8000,最高位只能取到8)
    //返回这一条件下,符合答案的数
    if(pos==-1)
        return (j+k)%2;
    if(!limit && !lead && dp[pos][j][k]!=-1)
        return dp[pos][j][k];

    int up = limit?bit[pos]:9; //范围到up
    ll ans=0;

    if(lead) {
        for(int i=0;i<=up;i++) {
            ans+=dfs(pos-1,i,1,i==0,limit && i==bit[pos]);
        }
    }
    else if((j+k)%2==1) {
        for(int i=0;i<=up;i++) {
            if((i+j)%2==0)
                ans+=dfs(pos-1,i,k+1,false,limit && i==bit[pos]);
            else
                ans+=dfs(pos-1,i,1,false,limit && i==bit[pos]);
        }
    }
    else {
        for(int i=0;i<=up;i++) {
            if((i+j)%2==0)
                ans+=dfs(pos-1,i,k+1,false,limit && i==bit[pos]);
        }
    }

    if(!limit && !lead)
        dp[pos][j][k] = ans;
    return ans;
}

ll solve(ll x) { //计算[1,x]内有少符合条件的数
    int pos=0;
    bit[0]=0;
    while(x) {//把数字x按位拆开放进数组里面,从最高位在pos-1,最低位是0
        bit[pos++]=x%10;
        x/=10;
    }
    // for(int i=0;i<pos;i++)
    //     printf("bit[%d] = %d\n", i,bit[i]);
    return dfs(pos-1,0,1,true,true);
}
int main() {
    scanf("%d",&t);
    memset(dp,-1,sizeof(dp));
    for(int i=1;i<=t;i++) {
        scanf("%I64d %I64d",&l,&r);
        printf("Case #%d: %I64d\n", i,solve(r)-solve(l-1));
    }
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值