BALNUM - Balanced Numbers 数位DP+三进制压缩 重要模板

BALNUM - Balanced Numbers

no tags 

 

Balanced numbers have been used by mathematicians for centuries. A positive integer is considered a balanced number if:

1)      Every even digit appears an odd number of times in its decimal representation

2)      Every odd digit appears an even number of times in its decimal representation

For example, 77, 211, 6222 and 112334445555677 are balanced numbers while 351, 21, and 662 are not.

Given an interval [A, B], your task is to find the amount of balanced numbers in [A, B] where both A and B are included.

Input

The first line contains an integer T representing the number of test cases.

A test case consists of two numbers A and B separated by a single space representing the interval. You may assume that 1 <= A <= B <= 1019 

Output

For each test case, you need to write a number in a single line: the amount of balanced numbers in the corresponding interval

Example

Input:
2
1 1000
1 9
Output:
147
4

算法分析:

题意:

给你一个范围[A,B],让你判断范围内所有数是否为平衡数,是平衡数的条件为:

1、对于每一位上的数字,如果是偶数,则必须出现奇数次。

2、对于每一位上的数字,如果是奇数,则必须出现偶数次。

分析:

数位dp

判断奇偶性这里,可以用三进制状态压缩。3进制有三个数012,则0表示未出现过,1表示出现奇数次,2表示出现偶数次,0~9的每个数字用一位三进制来表示其出现的奇偶次数。所以总共需要10位三进制数,

所以最大不超过6000

 

举个例子,2出现3次,5出现2次,9出现一次,其他未出现。则可以表示为:(001 002 000 1),对应下标表示出现数,从0开始

转换为10进制即为1989。这个十进制数本身并没有什么意义,只是方便存储。

最终我们可以定义dp数组:dp[20][6000].

 

解决完dp数组状态设计问题后,就需要解决状态转移问题。

每出现一个数字,我们就要将目前的十进制数转换为3进制数,然后将对应的数字的奇偶性做相应的改变。

最后还要转换为10进制数,以方便存储。

当达到边界条件时,即pos=-1时,只要判断当前0~9所有数字出现次数的奇偶性是否满足题目要求,

如果满足则增加答案1次,否则不改变。

这题需要考虑前导0情况

参考博客:https://blog.csdn.net/Kente_K/article/details/81699963

代码实现:
 

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
int a[105];
ll dp[105][60000];
int digit[105];//3-10进制转换
void getT(int x)//转换为三进制
{
    int cnt=0;
    for(int i=0;i<=9;i++)
    {
        digit[i]=x%3;
        x/=3;
    }
}
int getD()//转换为十进制
{
    int s=0,base=1;
    for(int i=0;i<=9;i++)
    {
        s+=digit[i]*base;
        base*=3; 
    }
    return s;
}
bool check(int x)//判断是否是平衡数
{
    getT(x);//将当前十进制的数转换为10位的三进制数
    for(int i=0;i<=9;i++)//每一位对于一个数字出现的奇偶次数
    {
        if(!digit[i]) continue;
        if((i&1)&&digit[i]==1)return 0;//是奇数并且出现奇数次,false
        else if(!(i&1)&&digit[i]==2) return 0;//是偶数并且出现偶数次,false
    }
    return 1;//全部满足条件,true
}
int change(int x,int k)//改变k这个数字出现的奇偶次数
{
    getT(x);
        if(digit[k]==0) digit[k]=1;//未出现过,加1次
        else if(digit[k]==1) digit[k]=2;//已出现奇数次,改变为偶数次
        else if(digit[k]==2) digit[k]=1;//已出现偶数次,改变为奇数次
    return getD();//三进制转换为十进制
}
ll dfs(int pos,int sta,int zero,bool limit)

{
    if(pos==-1)
    {
        if(zero) return 0;
        return check(sta);
    }
    if(!limit &&!zero&& dp[pos][sta]!=-1) return dp[pos][sta];
    int up=limit ? a[pos] : 9;
	ll tmp=0;
    for(int i=0;i<=up;i++)
    {
    	if(zero&&i==0)
		tmp+=dfs(pos-1,sta,zero&&i==0,limit && i==a[pos]);
		else 
		tmp+=dfs(pos-1,change(sta,i),zero&&i==0,limit && i==a[pos]);
	    
    }
    if(!limit&&!zero) dp[pos][sta]=tmp;
    return tmp;
}
ll solve(ll x)
{
    int pos=0;
    while(x)
    {
        a[pos++]=x%10;
        x/=10;
    }
    return dfs(pos-1,0,true,true);
}
int main()
{
	int T;
    scanf("%d",&T);
    memset(dp,-1,sizeof(dp));
    while(T--)
    {  
    	ll n,m;
    	scanf("%lld%lld",&n,&m);
        printf("%lld\n",solve(m)-solve(n-1));
    }
    return 0;
}

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值