题目描述
- 神犇最近闲来无事,于是就思考哲学,研究数字之美。在神犇看来,如果一个数的各位能够被分成两个集合,而且这两个集合里的数的和相等,那么这个数就是优美的(具体原因就只有神犇才知道了)。现在神犇在思考另一个问题,在区间[A,B]中有多少个数是优美的?这个问题对于神犇来说很简单,相信对于你来说也不难。
数据范围
A , B ≤ 1 0 9 A,B \le 10^9 A,B≤109
题目分析
- 这道题又是一个大坑,不能用数位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;
}