数谜

数谜

题目描述

数谜

小x认为自己是数学爱好者,但大Y并不这样认为。于是大Y让小x找出有多少个自然数接近n。我们认为一个数p在m下接近n,当且仅当:

  • 在十进制下,重新组织p的各位数字,可以使p变为n(举个例子:重新组织数字120,可且仅可得到120,102,210,201);
  • p没有前导0;
  • p是m的倍数。

x当然会算,但结果太大了,于是来找你帮忙。

输入格式

一行,两个正整数nmm100

输出格式

一行,一个数表示你给小x的答案。

输入样例1
104 2
输出样例1
3
样例解释1

这三个数满足条件:104, 140, 410

输入样例2
223 4
输出样例2
1
样例解释2

只有一个数2324下接近223

数据范围

测试点

n

1~2

<105

3~4

<1010

5~10

1018

    如果这题不是因为1018的庞大数据,而且没有为m倍数的限制,绝对是赤裸裸的水题,全部人当场开展“八仙过海,各显神通”各种骗分,但最后的结果都不多于40分。

大体思路:

    每一个数取的顺序不一,得出数也不一,可以用二进制表示每种情况,状压DP。

f[i][s][j]表示现在取到第i个数,状态为s,余数为j。会发现这样是会爆数组的。因为每一种状态s因每一位数取的顺序不一,得出的余数m也会是不一样的,所以可以去掉一维。

最后输出所有数都被选了的状态,并且余数为0。

具体实现:

    预处理算出每一位的数是少不了的,用数组记下,这样顺便就得出了n的长度。排个序,使它从小到大。这样能够确保每一次,当有相同的数时,都是选编号最小的那个,可以避免算重。

    并且记录每种状态选了几个数,也就是每种状态1的个数。

    zd给我们说的预处理一下它的余数,也是可以的,而且可以去掉一重循环。我们的方法有四重枚举,第一重枚举为第几个数( i );第二重表示状态( s );第三重枚举为余数( j );第四重表示这次要选第几个值( y )。(幸好时间有4秒)

    得出状态转移方程,f[s+(1<<y)][nowm]+=f[s][j];而nowm也就是现在的余数,要用到一个很神奇的东西,依靠前一位的余数便可以很快地用公式算出来,nowm=( j*10 + a[ y +1 ])%m。

    顺便解释一下关于y与a[y+1]的问题,第y个的值,注意因为一开始存进来的时候,每一个值的下标为1~ n的长度,而状态却是从0~ n的长度-1,这里容易搞混。

    Cosplay wyy:要想清楚!!!

边界处理:

    1、在确定这个状态(s)是否合法的时候,要看其中1的个数(选了几个数),如果不为i-1,即是不合法的。

    2、后面第四重for循环,要选第y个数时,要看状态s选了没有,如果选了,也不合法。

    3、如果是第一位数(i==1),那么不能选0,要判断a[y+1]是否为0,之所以要+1判断,前面已经解释过,a数组的坐标为1~n的长度,而y为0~n的长度-1,要区分开来。

    4、如果y不为0,即y+1不为1,也就是不是a[1],那么要看看它与前面一个数a[y]是否相等(a[y] ? a[y+1]),如果相等,就判断前一个有没有已经被选了,如果没有被选,那么此时这个便不能选。这样规定,是为了如果有重复的数字时,确保每次选都是选编号最小的。

    5、很重要!n要用long long,f也是。

代码如下:

#include
    
    
     
     
#include
     
     
      
      
#include
      
      
       
       
using namespace std;
const int maxs=(1<<18),maxn=20,maxm=105;
int m,e;
int sum[maxs];
long long f[maxs][maxm],a[maxs],n;
void check()
{
	for(int s=0;s<(1<
       
       
        
        0) sum[s]++; 
		}
	}
}

int main()
{
	freopen("2090.in","r",stdin);
	freopen("2090.out","w",stdout);
	cin>>n>>m;
	while(n)
	{
		int k=n%10;
		a[++e]=k;
		n=n/10;
	}
	sort(a+1,a+1+e);
	check();
	f[0][0]=1;
	for(int i=1;i<=e;i++)
	{
		for(int s=0;s<(1<
        
        
          0) continue; if(i==1&&a[y+1]==0) continue; if(y!=0&&a[y]==a[y+1]&&( s& (1<<(y-1)) )==0 ) continue; int nowm=(j*10+a[y+1])%m; f[s+(1< 
          
        
       
       
      
      
     
     
    
    


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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值