给出数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); }
时间: 2024-10-11 02:06:32