题目传送门
题意:
给你一个数 ,你可以对这个数的位进行重排,重排后的数不含前导 且是 的倍数的方案数。
数据范围: 。
题解:
这道题是看着数位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 ;
}