数谜
题目描述
数谜
小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 |
数据范围
本题求解方案数,而且每次在数的末尾加上一个数,具有明显阶段性,因而用动态规划来解决。
可以发现,n<1018, 因此可以使用状压DP。用set表示当前状态:其中的1表示这位数已经被用过,0则反之。
但是,题目中要求p要是m的倍数,似乎不太好判断是否合法。其实稍微转化一下,就是%m等于0的方案数。因而在DP的时候记录上这一余数,就能打破限制。
此外,即使set相同,但由于不同的排列方式,模m的余数也很有可能不相同,是不同的状态。所以余数应该记为一维。
于是,定义f[set][mod]表示 当前状态为set,并且当前组成的数模m等于mod的方案数。
状态转移:
外重循环枚举i ,当前是第几位数
嵌套枚举前一次的状态lset,其中lset中1必须为i-1个
嵌套枚举上次的余数lmod
嵌套枚举这一位选的数now
[当i==1时,不能选0。而且,由于每种数可能有多个(比如有3个1, 不管选1的顺序如何都是同一种方案),会造成重复。因而要规定顺序:每种数必须从第一个开始选起]
f[newset][newmod]+=f[lset][mod];
最后的答案即为f[(1<<位数)-1][0];
代码如下:
#include
#include
#include
using namespace std;
const long long MAXN=20,MAXS=(1<<18),MAXM=105;
int m,num;
long long f[MAXS][MAXM];//状态为set,%m的余数。
long long a[MAXN],nums[MAXS],n,t;
void init()
{
t=n,num=0;
while(t)
{
a[++num]=t%10;
t/=10;
}
sort(a+1,a+1+num);
}
void prepare()
{
for(int i=0;i<(1<
0) nums[i]++;
}
}
}
void dp()
{
f[0][0]=1;
for(int i=1;i<=num;i++)//枚举到了第i位
{
for(int set=0;set<(1<
0) continue; if(i==1&&a[now+1]==0) continue; if(now!=0&&a[now]==a[now+1]&&(set&(1<<(now-1)))==0) continue; f[set+(1<
>n>>m; init(); prepare(); dp(); cout<
<
<
看到题目,一定要注意题目是否具有阶段性,考虑DP求解。在数据量较小,不超过20时,想想状压DP。
在题目限制棘手的时候,要懂得转换。如果某个限制似乎要通过枚举才能解决,那就运用种种定理,想想办法让它在递推时能够转移。
其实,这题写到这并没有想象中的那么难,状压DP也是之前学过的,考试时为什么怎么都想不到呢?一个是看到位数为18,却没有往状压DP上想过。另外,也没有想过用在DP时记录余数的办法去除倍数这一限制。
总而言之,还是对DP掌握不够好,同时处理问题不够灵活啊。。。。。