SPOJ10606Balanced Numbers(数位DP)

传送门 
题目大意:一个数被称为是平衡的数当且仅当对于所有出现过的数位,偶数出现奇数次,奇数出现偶数次。 
给定 A B ,请统计出 [A,B] 内所有平衡的数的个数。

注意,这里的偶数是指出现过的数,并且不能计算前导零。

对于每一个数有三种状态: 
0 :这个数还木有出现过。 
1 :这个数出现过奇数次。 
2 :这个数出现过偶数次。 

于是乎用一个三进制数来表示每一种状态。

3的10次方是59049,题目要求计算的数组最大为19位数,所以dp数组开成dp[20][60000],第二维表示状态。


可能是oj时间限制得比较严,用scanf和printf会超时

具体解释请看代码:

#include<iostream>
#include<cstring>
#include<cstdio>
using namespace std;
typedef long long LL;
LL dp[20][60000];
int bit[20];//保存数的每一位
bool check(int s)
{
    int num[10];
    for(int i = 0; i < 10; i++)//从低位到高位获取三进制状态的每一位
    {
        num[i] = s % 3;
        s /= 3;
    }
    for(int i = 0; i < 10; i++)
        if(num[i] != 0)//如果有不符合要求的,就直接返回0,该数不作为统计
        {
            if(i % 2 == 0 && num[i] == 2)
                return false;
            if(i % 2 == 1 && num[i] == 1)
                return false;
        }
    return true;//符合题目要求,则算一个
}
int getnews(int x, int s)
{
    int num[10];
    for(int i = 0; i < 10; i++)
    {
        num[i] = s % 3;
        s /= 3;
    }
    if(num[x] == 0)
        num[x] = 1;
    else
        num[x] = 3 - num[x];
    int news = 0;
    for(int i = 9; i >= 0; i--)
    {
        news *= 3;
        news += num[i];
    }
    return news;
}
LL dfs(int pos, int s, bool lim, bool z)//z用来判断是否一直是前导0
{
    if(pos == -1)//递归到了个位且枚举完成后,判断数是否符合要求
        return check(s);
    if(!lim && dp[pos][s] != -1)//如果没有达到上限且这个状态之前已经被记录过,直接返回
        return dp[pos][s];
    LL ans = 0;
    int End = lim ? bit[pos] : 9;
    for(int i = 0; i <= End; i++)//枚举下一位的每一位数
        ans += dfs(pos-1, (z&&i==0) ? 0 : getnews(i, s), lim && i == End, z&&i==0);//getnews是在确定下一位后获取新的状态的,如果一直是前导0,那么状态就是0
    if(!lim)
        dp[pos][s] = ans;
    return ans;
}
LL calc(LL n)
{
    int len = 0;
    while(n)
    {
        bit[len++] = n % 10;
        n /= 10;
    }
    return dfs(len-1, 0, 1, 1);
}
int main()
{
    int T;
    LL l, r;
    memset(dp, -1, sizeof(dp));
    cin >> T;
    while(T--)
    {
        cin >> l >> r;
        cout << calc(r) - calc(l-1) << endl;
    }
    return 0;
}


  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值