JZOJ 3362. 【NOI2013模拟】数数(DFS) JZOJ 3362. 【NOI2013模拟】数数

题目

Description

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

Input

输入只有一行,包含两个整数A和B。

Output

输出只有一行,包含一个整数,代表区间[A,B]中优美的数的个数。

Sample Input

样例1:
1 11

样例2:
6354 234363

Sample Output

样例1:
1

样例2:
82340

Data Constraint

对于30%的数据, 1 ≤ A ≤ B ≤ 1000 1≤A≤B≤1000 1AB1000.
对于50%的数据, 1 ≤ A ≤ B ≤ 1 0 7 1≤A≤B≤10^7 1AB107.
对于100%的数据, 1 ≤ A ≤ B ≤ 1 0 9 1≤A≤B≤10^9 1AB109.

题解

  • 题外话:
  • 这题可以打表,用不长的时间先暴搜出每 1 0 6 10^6 106的答案的前缀和(分块打表),
  • 询问时只用暴力计算两头不完整的块的答案,可过!!!
  • 以下进入正题——
  • 这题需要很多铺垫,
  • 首先想想怎么判断每个数是否为优美的?
  • 方法一:DFS枚举每一位放集合 1 1 1还是集合 2 2 2,然后判断两集合元素之和是否相等;
  • 方法二:设 s u m sum sum为每一位的数之和,若其为奇数,自然无解,否则用背包( 0 / 1 0/1 0/1)看看每一位的数是否可以组合成 s u m 2 frac{sum}{2} 2sum
  • 方法三:直接用一个变量 p p p l o n g l o n g long long longlong)在二进制下代替背包(自己手推一下),每加入一位 x x x,直接 p = p ∣ ( p &lt; &lt; x ) p=p|(p&lt;&lt;x) p=p(p<<x)
  • 尽管这些方法一个比一个快,但终究是对于每一个数单独判断的,既然范围最大达到了 1 0 9 10^9 109,那时间还是会超限,
  • 所以自然想到不能单独判断每个数。
  • 按照一般的思路,我们可以设 A n s [ i ] Ans[i] Ans[i] [ 1 , i ] [1,i] [1,i]的优美的数的个数,那么答案为 A n s [ B ] − A n s [ A − 1 ] Ans[B]-Ans[A-1] Ans[B]Ans[A1]
  • 现在的问题变成了怎么求 A n s [ x ] Ans[x] Ans[x]
  • x x x的位数为 L L L,暴力DFS枚 0 − 9 0-9 09各选择几个(时间极小),满足个数之和为 L L L
  • 接着按照上面最快的方法三判断这些数是否可行,不可行直接舍去,
  • 可行的话则计算这些数有多少排列方式,
  • 这绝对不是一条式子就能完成的事,因为需要保证排列后的数不超过上限 x x x
  • 设DFS出的 0 − 9 0-9 09所选的个数为 a [ i ] ( i ∈ [ 0 , 9 ] ∩ i ∈ N ) a[i](iin[0,9]∩iin N) a[i](i[0,9]iN)
  • 再来一个DFS从高位往低位填数
  • 每到一位先来统计答案
  • 强制限定这一位所填小于 x x x的这一位,设填数为 p p p,需保证还有 p p p可选,也就是当前 a [ p ] &gt; 0 a[p]&gt;0 a[p]>0(显然),
  • 先给 a [ p ] − 1 a[p]-1 a[p]1
  • 接着 A n s = A n s + ( ∑ a [ i ] ) ! Π a [ i ] ! Ans=Ans+frac{(sum a[i])!}{Pi a[i]!} Ans=Ans+Πa[i]!(a[i])!(因为当前位已经小于了,所以剩下的数可以任意顺序填,同时除去相同的数顺序调换的方案),
  • 再给 a [ p ] + 1 a[p]+1 a[p]+1
  • 接着再来向后填数
  • 这是强制限定这一位所填等于 x x x的这一位,
  • 保证 a [ p ] &gt; 0 a[p]&gt;0 a[p]>0,给 a [ p ] − 1 a[p]-1 a[p]1,DFS进去后一位,回溯时 a [ p ] + 1 a[p]+1 a[p]+1
  • 这样就能保证每种填数的情况是不重不漏的了,
  • 但是,我们会发现,这样填下去在统计答案时(见上方)怎样填出的数总是小于原数 x x x
  • 所以最终的答案应该为 A n s [ B + 1 ] − A n s [ A ] Ans[B+1]-Ans[A] Ans[B+1]Ans[A].

代码

#include<cstdio>
#include<cstring>
using namespace std;
#define LL long long
int c[11],a[10],ans,ls,f[10];
void dfs1(int k)
{
	if(k>ls) return;
	for(int i=0;i<c[ls-k+1];i++) if(a[i]) 
	{
		a[i]--;
		int s=0;
		for(int j=0;j<=9;j++) s+=a[j];
		s=f[s];
		for(int j=0;j<=9;j++) s/=f[a[j]];
		ans+=s;
		a[i]++;
	}
	int i=c[ls-k+1];
	if(a[i]) a[i]--,dfs1(k+1),a[i]++;
}
void dfs(int k,int s,int sum,int x)
{
	if(k>9)
	{
		if(sum%2) return;
		if(a[0]==ls) return ;
		LL p=1;
		for(int i=0;i<=9;i++) 
		{
			for(int j=1;j<=a[i];j++) 
			{
				p=(p<<i)|p;
				p%=(LL)1<<(LL)50;
			}
		}
		LL r=(LL)1<<(LL)(sum/2);
		if((p&r)==0) return;
		dfs1(1);
		return;
	}
	if(k==9) 
	{
		a[k]=ls-s;
		dfs(k+1,ls,sum+9*(ls-s),x);
		return;
	}		
	for(int i=0;i<=ls-s;i++)
	{
		a[k]=i;
		dfs(k+1,s+i,sum+k*i,x);
	}
}
int count(int x)
{
	if(x==0) return 0;
	int t=x;ls=0;
	while(t) c[++ls]=t%10,t/=10;
	ans=0;
	memset(a,0,sizeof(a));
	dfs(0,0,0,x);
	return ans;
}
int main()
{
	int a,b;
	scanf("%d%d",&a,&b);
	f[0]=1;
	for(int i=1;i<=9;i++) f[i]=f[i-1]*i;
	printf("%d",count(b+1)-count(a));
	return 0;
}