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 ;
}

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章