给出数n和m,求n的所有排列中,模m得0的有多少个 n (1 ≤ n < 1018) and m (1 ≤ m ≤ 100).
暴力法我们直接枚举n的所有排列,显然18!超时。
考虑怎么dp
假设给了我们数n=23765
显然有
(237%m*10+6)%m=2376%m
(367%m*10+2)%m=3672
我们很自然的想到了
这样的状态转移
dp[i][k]
i代表取的数的状态
代表在取数状态为i的情况下模m为k的数有多少
比如
对于23765的356
取数状态为01011
dp方程就是
dp[i|(1<<j)][(k*10+n[j])%m](j位还没取)+=dp[i][k]
显然,dp[i|(1<<j)][(k*10+n[j])%m]这个状态是多次可达的,所以我们要+=
这里默认是把新数加到前一个状态所有组成的数的末尾,因为加到中间的情况可以由其他状态计算进去
比如
对356来说,现在要加入2
我们加入末尾3562,由上面的柿子算入
如果加到中间,比如成了3256,则这个状态是由325取6得到3256计算
最后,我们这样做是把所有的数字当做互不相同处理的,对相同的数,我们要除以所有重复的情况,即所有相同次数的阶乘的积
#include <iostream>
#include <cstdio>
#include <cstring>
#include <cmath>
#include <algorithm>
using namespace std;
const long long NN=1111;
char str[NN];
long long times[NN];
long long dp[(1<<18)][111];
int main(){
#ifndef ONLINE_JUDGE
freopen("/home/rainto96/in.txt","r",stdin);
#endif
long long m;scanf("%s%I64d",str,&m);
long long len=strlen(str);
long long all=(1<<len)-1;
long long left=1;
for(long long i=0;i<len;i++)
left*=++times[str[i]-='0'];
dp[0][0]=1;
for(long long i=0;i<=all;i++){
for(long long j=0;j<len;j++){
if(!(i&(1<<j))){
if(i || str[j]){
for(long long k=0;k<=m-1;k++){
dp[i|(1<<j)][(k*10+str[j])%m]+=dp[i][k];
}
}
}
}
}
printf("%I64d\n",dp[all][0]/left);
}