zoukankan      html  css  js  c++  java
  • 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;
    }
    
    哈哈哈哈哈哈哈哈哈哈
  • 相关阅读:
    【VUE】自定义组件
    【docker】Dockerfile
    【docker】常用命令
    【Java Web开发学习】跨域请求
    xshell6和xftp6运行提示缺少mfc110u.dll文件的解决办法
    【NPM】使用问题记录
    【Zuul】使用学习
    第八章 泛型程序设计
    第九章 集合
    分布式系列二: 分布式系统的通信
  • 原文地址:https://www.cnblogs.com/LZA119/p/13910071.html
Copyright © 2011-2022 走看看