codeforces401D 2000分状压dp + 去重

题目传送门

题意:

给你一个数 n ,你可以对这个数的位进行重排,重排后的数不含前导 0 且是 m 的倍数的方案数。

数据范围: 1 \leqslant n \leqslant 10^{18} \;,\; 1 \leqslant m \leqslant 100 。 

题解:

这道题是看着数位dp的练习题刷的,发现没法记忆化搜索。

然后是状压dp。

因为最多有 18 位数,所以我们考虑状压每位数,转移时多出的一位数放在末尾即可。

dp[i][j] 表示状压的状态是 i 并且模 m 的余数是 j 的方案数。

转移方程:dp[i \;|\; (1 << k)][(j * 10 + b[k]) \;\%\; m] \;+= dp[i][j] ,  并且 (i \;\&\; (1 << k)) = 0 。

最后要去重,这样其实把两个相同的数字认为是不同的,要除以相同数字的个数的阶乘。

感受:

有的题看起来是数位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 ;
}

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值