数谜
题目描述
数谜
小x认为自己是数学爱好者,但大Y并不这样认为。于是大Y让小x找出有多少个自然数接近n。我们认为一个数p在m下接近n,当且仅当:
- 在十进制下,重新组织p的各位数字,可以使p变为n(举个例子:重新组织数字120,可且仅可得到120,102,210,201);
- p没有前导0;
- p是m的倍数。
小x当然会算,但结果太大了,于是来找你帮忙。
输入格式
一行,两个正整数n,m。m≤100。
输出格式
一行,一个数表示你给小x的答案。
输入样例1
104 2
输出样例1
3
样例解释1
这三个数满足条件:104, 140, 410。
输入样例2
223 4
输出样例2
1
样例解释2
只有一个数232在4下接近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<