3362. 【NOI2013模拟】数数

题目描述
  • 神犇最近闲来无事,于是就思考哲学,研究数字之美。在神犇看来,如果一个数的各位能够被分成两个集合,而且这两个集合里的数的和相等,那么这个数就是优美的(具体原因就只有神犇才知道了)。现在神犇在思考另一个问题,在区间[A,B]中有多少个数是优美的?这个问题对于神犇来说很简单,相信对于你来说也不难。
数据范围

A , B ≤ 1 0 9 A,B \le 10^9 A,B109

题目分析
  • 这道题又是一个大坑,不能用数位DP。
  • 主要原因是无法去重,比如 1111 1111 1111,它可以有多种分成两个集合的方法。
  • 然后当时做这道题所以我蒙圈了。
  • 结果就像暴力用递归枚举每一个数字(0~9)出现过多少次。
  • 接着就发现:哎?好像不会超时哎!跑得还飞快!
  • 在里面套了一个用来判断是否能够分成两个集合的背包后依然跑得很快。
  • 关键是要求这种数字组合下有多少种方案是小于等于当前要求的上限的。
  • 这个慢慢求,枚举压到多少位(假如一个数 12345 12345 12345,你定前三个数为 123 123 123,那么后面你不能超过 4 4 4,所以你压了 3 3 3位,如果前三个数为 122 122 122,那么后面可以随意填,但你第三位绝不能达到 3 3 3,你只压了 2 2 2位),甚至还可以用数位DP来求 ,然后组合数上一波。
  • 然后兴奋至极,没注意到这道题还有另一种方法。
  • 打表
  • 我们发现可以直接每一百万个为一组暴力求解,对于整组我们打表,剩下一个一个自己求,然后就过了!
  • 我自闭了…
代码(非打表)
#include<cstdio>
#include<cstring>
using namespace std;
const int O=90;
bool f[12][183];int b[12],ans;
int g[12],len,C[12][12],B[12];
void dfs(int x,int s){
	if(x==10){
		if(!f[9][O]) return ;
		if(s==0) return ;
		int d=1;
		for(int i=0;i<=9;i++) B[i]=b[i];
		for(int i=1;i<=s;i++) d*=i;
		for(int i=0;i<=9;i++)
			for(int j=1;j<=B[i];j++) d/=j;
		if(s<len) return ;
		for(int i=len;i>=1;i--){
			for(int j=0;j<g[i];j++)
				if(b[j])
					ans+=d*B[j]/s;
			if(!B[g[i]]) break;
			else d=d*B[g[i]]/s,s--,B[g[i]]--;
		}
		if(s==0) ans+=d;
		return ;
	}
	for(int i=0;i<=len-s;i++){
		if(i&&x){
			for(int j=0;j<=180;j++) f[x][j]=false;
			for(int j=0;j<=180;j++){
				for(int k=-i;k<=i;k+=2){
					if(j>=k*x&&f[x-1][j]) f[x][j-k*x]=true;
					if(j<=180-k*x&&f[x-1][j]) f[x][j+k*x]=true;
				}
			}
		}
		else if(i==0&&x) for(int j=0;j<=180;j++) f[x][j]=f[x-1][j];
		b[x]=i;
		dfs(x+1,s+i);
		b[x]=0;
	}
}
int main()
{
	int l,r;scanf("%d%d",&l,&r);
	len=0;while(r) g[++len]=r%10,r/=10;
	memset(f,false,sizeof(f));
	f[0][O]=true;ans=0;dfs(0,0);
	if(l==1){
		printf("%d\n",ans);
		return 0;
	}
	l--;len=0;while(l) g[++len]=l%10,l/=10;
	memset(f,false,sizeof(f));
	f[0][O]=true;int t=ans;ans=0;dfs(0,0);
	printf("%d\n",t-ans);
	return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值