Codeforces Round #235 (Div. 2) D Roman and Numbers(状态DP)

Roman is a young mathematician, very famous in Uzhland. Unfortunately, Sereja doesn't think so. To make Sereja change his mind, Roman is ready to solve any mathematical problem. After some thought, Sereja asked Roma to find, how many numbers are close to number n, modulo m.

Number x is considered close to number n modulo m, if:

  • it can be obtained by rearranging the digits of number n,
  • it doesn't have any leading zeroes,
  • the remainder after dividing number x by m equals 0.

Roman is a good mathematician, but the number of such numbers is too huge for him. So he asks you to help him.

Input

The first line contains two integers: n (1 ≤ n < 1018) and m (1 ≤ m ≤ 100).

Output

In a single line print a single integer — the number of numbers close to number n modulo m.

  • 数位DP,用dp[mask][m]记录余数为m的个数,mask标记使用了哪些位。
  • mask相当于做了一个hash。
   eg: 数1234
         1         -> 1
       10         -> 2
       11         -> 21  、 21
      100         ->3
      101        ->31  、13
      110        ->32   、23
      111        ->321  、 312 、 213 , 231 , 123 ,132  也就是相应位置3个数的组合
     1000      ->4
     1001      ->41   ,14  也就是相应2个数位的组合 
    111  这个状态3个数字都出现;  可以有“32” +“1” , “31” +“2”,......
                         就是由2个数字尾巴加1个数字转移得来。也就是说从下往上跟新状态。 
  • 避免0开头,将非0的单个数字的对应为赋值为1。
  • 将数字从大到小排序,每次添加一个状态相当于在任意数字组合的末尾添加一个数字dp[mask & (1 << i)][(m * 10 + digit[i]) % m] += dp[mask][m]。
  • 结果为dp[(1 << digitNum) - 1][0]除以排列数。
typedef long long LL ;
LL dp[1<<18][101] ;
LL num[18] ;
LL repeat_dig[10] ;
LL fac[19] ;

LL DP(string str , int Mod){
   int i , j , n , k ;
   LL repeat = 1 ;
   memset(repeat_dig , 0 , sizeof(repeat_dig)) ;
   memset(dp , 0 , sizeof(dp)) ;
   n = str.length() ;
   for(i = 0 ; i < n ; i++){
       num[i] = str[i] - '0' ;
       repeat_dig[num[i]]++ ;
   }
   for(i = 0 ; i <= 9 ; i++)
       repeat *= fac[repeat_dig[i]] ;
   sort(num , num + n) ;
   for(i = 0 ; i < n ; i++){
      if(num[i] > 0)
         dp[1<<i][num[i]%Mod] = 1 ;
   }
   for(i = 1 ; i < (1<<n) ; i++){
     for(j = 0 ; j < Mod ; j++){
         if(dp[i][j]){
             for(k = 0 ; k < n ; k++){
                 if((i & (1<<k)) == 0)
                    dp[i|(1<<k)][(j*10 + num[k]) % Mod] += dp[i][j] ;
             }
         }
     }
   }
   return dp[(1<<n)-1][0]/repeat ;
}

int main(){
   fac[0] = 1 ;
   for(int i = 1 ; i <= 18 ; i++)
      fac[i] = fac[i-1] * i ;
   string s ;
   int mod ;
   while(cin>>s>>mod){
        cout<<DP(s, mod)<<endl ;
   }
   return 0 ;
}



记忆化搜索(好懂):
typedef long long LL ;
LL dp[1<<18][101] ;
LL num[18] ;
LL repeat_dig[10] ;
LL fac[19] ;
bool visited[1<<18] ;
int n , Mod ;
void dfs(int state){
   if(visited[state])
      return ;
   visited[state] = 1 ;
   for(int i = 0 ; i < n ; i++){
       if((state | (1<<i)) != state)
          continue ;
       int now = state - (1<<i) ;
       if(now == 0)
          continue ;
       dfs(now) ;
       for(int j = 0 ; j < Mod ; j++)
         dp[state][(j*10 + num[i]) % Mod]  += dp[now][j] ;
   }
}

LL DP(string str){
   int i , j  , k ;
   LL repeat = 1 ;
   memset(repeat_dig , 0 , sizeof(repeat_dig)) ;
   memset(dp , 0 , sizeof(dp)) ;
   memset(visited , 0 , sizeof(visited)) ;
   n = str.length() ;
   for(i = 0 ; i < n ; i++){
       num[i] = str[i] - '0' ;
       repeat_dig[num[i]]++ ;
   }
   for(i = 0 ; i <= 9 ; i++)
       repeat *= fac[repeat_dig[i]] ;
   for(i = 0 ; i < n ; i++){
       if(num[i])
           dp[1<<i][num[i]%Mod] = 1 ;
   }
   dfs((1<<n)-1) ;
   return dp[(1<<n) -1][0]/repeat ;
}

int main(){
   fac[0] = 1 ;
   for(int i = 1 ; i <= 18 ; i++)
      fac[i] = fac[i-1] * i ;
   string s ;
   while(cin>>s>>Mod){
        cout<<DP(s)<<endl ;
   }
   return 0 ;
}


  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值