題目傳送門
題意:
給你一個數 ,你可以對這個數的位進行重排,重排後的數不含前導 且是 的倍數的方案數。
數據範圍: 。
題解:
這道題是看着數位dp的練習題刷的,發現沒法記憶化搜索。
然後是狀壓dp。
因爲最多有 位數,所以我們考慮狀壓每位數,轉移時多出的一位數放在末尾即可。
表示狀壓的狀態是 並且模 的餘數是 的方案數。
轉移方程: , 並且 。
最後要去重,這樣其實把兩個相同的數字認爲是不同的,要除以相同數字的個數的階乘。
感受:
有的題看起來是數位dp,其實是狀壓dp。
注意去重。
代碼:
#include<bits/stdc++.h>
using namespace std;
typedef long long ll ;
const int maxn = 2e5 + 5 ;
ll dp[1 << 18][105] ;
ll n ;
int m , len = 0 ;
int b[25] , num[15] ;
void solve()
{
int up = 1 << len ;
ll ans ;
memset(dp , 0 , sizeof(dp)) ;
for(int i = len - 1 ; i >= 0 ; i --)
if(b[i] > 0) dp[1 << i][b[i] % m] = 1 ;
for(int i = 1 ; i < up ; i ++)
{
for(int j = 0 ; j < m ; j ++)
{
if(dp[i][j] == 0) continue ;
for(int k = 0 ; k < len ; k ++)
{
if((i & (1 << k)) > 0) continue ;
int nxt = (j * 10 + b[k]) % m ;
dp[i | (1 << k)][nxt] += dp[i][j] ;
}
}
}
ans = dp[(1 << len) - 1][0] ;
for(int i = 0 ; i < len ; i ++) num[b[i]] ++ ;
for(int i = 0 ; i < 10 ; i ++)
for(int j = 1 ; j <= num[i] ; j ++)
ans /= j ;
printf("%lld\n" , ans) ;
}
int main()
{
scanf("%lld%d" , &n , &m) ;
while(n > 0) b[len ++] = n % 10 , n /= 10 ;
solve() ;
return 0 ;
}