題意:給你數n和m,求n的各位重排後有多少個滿足膜m==0?
思路:由於n的範圍最大可以到18位,故不能暴力枚舉(會超時),那麼思路就放在了帶記憶化的枚舉上,數位dp。重點在於如何記憶化,比如現在枚舉到了第pos位,然後還需要剩下待定的各位和膜m爲mmod,在什麼情況下就能直接返回已知結果?當然是當待定的各位還有哪些選擇和mmod都確定時結果便確定了:
這便是dp數組:dp[狀態(還有那些數待安排)][mmod]
注:此處dp數組並沒有pos這一維度,原因在於狀態中就已經包含pos此維度了。
而下一個問題是如何表示狀態——n的各位中待安排的數,這裏就可以用到狀態壓縮的方式,將狀態設成二進制串,每個二進制位對應n的數位上的數,取過就爲0,待取就爲1。
如此這般,要求的最終結果便是dp[111...1][0] 其中狀態是strlen(n)個1
代碼如下:
#include<stdio.h>
#include<string.h>
#include<algorithm>
using namespace std;
int con[10],a[20];
long long dp[(1<<18)+2][105],dec[18];
int m,len;
long long dfs(int pos,int sta,int mmod,int lead)
{
int tem[20];
if(pos==-1)
{
if(sta!=0) printf("%d error!\n",sta);
if(mmod==0) return 1;
else return 0;
}
if(!lead && dp[sta][mmod]!=-1) return dp[sta][mmod];
long long ans=0;
memset(tem,0,sizeof(tem)); //避免重複
for(int i=0;i<=len;i++)
if(sta&(1<<i))
{
if(lead && a[i]==0) continue;
if(tem[a[i]]) continue; //若此位以試探過a[i],則跳過(目的是去重)
tem[a[i]]=1;
ans+=dfs(pos-1,sta-(1<<i),(mmod-dec[pos]*a[i]%m)%m,0);
}
if(!lead) dp[sta][mmod]=ans;
return ans;
}
long long stat(long long x)
{
memset(con,0,sizeof(con));
int i=0;
while(x>0)
{
int tem=x%10;
con[tem]++;
a[i++]=tem;
x/=10;
}
len=i-1;
return dfs(len,(1<<i)-1,0,1);
}
int main()
{
long long n;
scanf("%lld%d",&n,&m);
memset(dp,-1,sizeof(dp));
dec[0]=1;
for(int i=1;i<18;i++) dec[i]=dec[i-1]*10;
printf("%lld\n",stat(n));
return 0;
}