(SPOJ - BALNUM)Balanced Numbers(数位DP+状态压缩)

题目链接:https://vjudge.net/problem/SPOJ-BALNUM#author=xnkmyson

题意:求给定区间,一个数的数位上每个奇数出现偶数次,每个偶数出现奇数次,这样数的个数

首先我需要说明一个误区,一个数出现0次与出现偶数次是有区别的,这也是这道题目的一个坑点,一个偶数每出现过也是符合题意的并不能就认为其出现了偶数次

我们f数组需要记录pos,这个是肯定的,还需要记录什么呢?一个是当前0~9这几个数是否出现过,因为每出现过的我们就不需要考虑了,还有一个就是0~9这几个数出现了几次,具体来说是统计0~9这几个数是出现了奇数次还是偶数次,这也是我们最后判断一个数是否满足答案的标准,既然是记录状态,我们也很容易想到用一个整数来表示当前的状态,更新就比较简单了,如果出现了几,就把第几位的状态标志为出现过,编程角度就是或上2的几次方,也需要把该数出现次数的奇偶性进行改变,编程角度就是异或上2的几次方,这都是比较容易实现的。

这道题还需要考虑的一个点就是前导0,因为前导0是不能计入0出现的次数的,所以我们需要单独考虑,不过也不是很麻烦,我在代码中有解释,详情可以参考代码:

#include<cstdio>
#include<cstring>
#include<algorithm>
#include<iostream>
#include<queue>
using namespace std;
const int N=20;
typedef long long ll;
ll f[N][1<<10][1<<10],a[N];//f[i][s]表示遍历到第i位,且此时状态为j(第j位为1就代表几出现了奇数次)的合法数量 
ll dp(int pos,int st,int s,int lead,int limit)
{
	if(!pos)
	{
		for(int i=0;i<10;i++)
		{
			if(st>>i&1)
			{
				if((i&1)&&(s>>i&1)) return 0;//奇数出现奇数次
				else if(!(i&1)&&!(s>>i&1)) return 0;//偶数出现偶数次
			}
		}
		return 1;
	}
	if(!lead&&!limit&&f[pos][st][s]!=-1) return f[pos][st][s];
	int up=limit?a[pos]:9;
	ll ans=0;
	for(int i=0;i<=up;i++)
	{
		if(lead)
		{
			if(i)//终止前导0 
				ans+=dp(pos-1,st|(1<<i),s^(1<<i),0,limit&&(i==up));
			else//继续前导0 
				ans+=dp(pos-1,st,s,1,limit&&(i==up));//前导0不能计入0出现的次数 
		}
		else
	    	ans+=dp(pos-1,st|(1<<i),s^(1<<i),lead&&(i==0),limit&&(i==up));
	}
	if(!lead&&!limit) f[pos][st][s]=ans;
	return ans;
}
ll solve(ll x)
{
	if(!x) return 1;
	int pos=0;
	while(x)
	{
		a[++pos]=x%10;
		x/=10;
	}
	return dp(pos,0,0,1,1);
}
int main()
{
	int T;
	cin>>T;
	memset(f,-1,sizeof f);
	while(T--)
	{
		ll l,r;
		scanf("%lld%lld",&l,&r);
		printf("%lld\n",solve(r)-solve(l-1));
	}
	return 0;
}

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值