给你一个数n,我们可以将这个数随意打乱顺序,重新排列,问一共有多少种排列方式,使得其没有前导0并且%m ==0;
思路:
1、考虑到数n长度只有18,那么我们考虑状压dp,设定dp【i】【j】表示已经排列完的数字的状态为i,其当前%m剩余的值为j的方案数。
2、那么不难推出其状态转移方程:
dp【i】【(k*10+a[j])%m】=dp【i-(1<<j)】【k】;
其中i&(1<<j)!=0,表示对状态i的转移,从没有将j这个数排列到排列了j这个数的状态转移。
秘制cache...如果设定dp【100】【1<<18】就会TLE,反过来设定成dp【1<<18】【100】就可以过...................................
3、注意前导0的把控,注意结果因为会有重复,那么我们将答案除以从0到9的数字出现的次数的阶乘。
Ac代码:
#include<stdio.h>
#include<string.h>
using namespace std;
#define ll __int64
ll jiecheng[20];
char aa[20];
ll vis[20];
int a[20];
ll dp[(1<<18)][100];
int m;
void init()
{
ll sum=1;
jiecheng[0]=1;
for(int i=1;i<=19;i++)
{
sum*=i;
jiecheng[i]=sum;
}
}
int main()
{
init();
while(~scanf("%s%d",aa,&m))
{
memset(dp,0,sizeof(dp));
memset(vis,0,sizeof(vis));
int n=strlen(aa);
int zero=(1<<n)-1;
for(int i=0;i<n;i++)
{
a[i]=aa[i]-'0';
vis[a[i]]++;
if(a[i]==0)zero-=(1<<i);
}
for(int i=0;i<n;i++)
{
dp[(1<<i)][a[i]%m]=1;
}
for(int i=0;i<(1<<n);i++)
{
for(int j=0;j<n;j++)
{
if((i&(1<<j))!=0)
{
for(int k=0;k<m;k++)
{
if((zero&(i-(1<<j)))==0)continue;//去掉前导0的情况
dp[i][(k*10+a[j])%m]+=dp[i-(1<<j)][k];
}
}
}
}
ll output=dp[(1<<n)-1][0];
for(int i=0;i<=9;i++)output/=jiecheng[vis[i]];
printf("%I64d\n",output);
}
}