Codeforces Round #235 (Div. 2) D. Roman and Numbers (数位dp、状态压缩)

D. Roman and Numbers
time limit per test
4 seconds
memory limit per test
512 megabytes
input
standard input
output
standard output

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.

Sample test(s)
input
104 2
output
3
input
223 4
output
1
input
7067678 8
output
47
Note

In the first sample the required numbers are: 104, 140, 410.

In the second sample the required number is 232.


题意:

给定一个数字n。将n的每一位数字又一次排列,求在这些排列数之中能够被n整除的方法数。


思路1:

数位dp。数字仅仅有18位,能够考虑位压缩。

dp[i][j]表示状态所用的状态为i。前缀模m余j的个数。

枚举下一位选哪一个数来进行状态转移。

用记忆化搜索来实现。注意不能有前导0。

如何解决不反复计算呢,採用的方法是每次转移的时候不反复转移,用一个数组vis[10]来标记选了什么,比方计

算了3之后,后面还有3就不选了,就不会计算反复了。


代码:

#include <iostream>
#include <cstdio>
#include <cstring>
#include <algorithm>
#include <cmath>
#include <string>
#include <map>
#include <stack>
#include <vector>
#include <set>
#include <queue>
#define maxn 205
#define MAXN 100005
#define mod 100000000
#define INF 0x3f3f3f3f
#define pi acos(-1.0)
#define eps 1e-6
typedef long long ll;
using namespace std;

ll n,m,ans,tot,ed;
ll dig[20],dp[1<<18][105];

ll dfs(ll s,ll sy,ll flag)
{
    if(s==ed)
    {
        if(sy==0) return 1;
        return 0;
    }
    if(dp[s][sy]!=-1) return dp[s][sy];
    ll i,t,best=0;
    bool vis[10]={0};
    for(i=0;i<tot;i++)
    {
        if(vis[dig[i]]||(s&(1<<i))) continue ;
        if(flag==0&&dig[i]==0) continue ;
        ll ss=s|(1<<i),ty=(sy*10+dig[i])%m;
        vis[dig[i]]=1;
        best+=dfs(ss,ty,1);
    }
    dp[s][sy]=best;
    return best;
}
void solve()
{
    ll i,j,t,x;
    x=n;
    memset(dig,0,sizeof(dig));
    memset(dp,-1,sizeof(dp));
    tot=0;
    while(x)
    {
        t=x%10;
        dig[tot++]=t;
        x/=10;
    }
    ed=(1<<tot)-1;
    ans=dfs(0,0,0);
}
int main()
{
    ll i,j,t;
    while(~scanf("%I64d%I64d",&n,&m))
    {
        solve();
        printf("%I64d\n",ans);
    }
    return 0;
}


思路2:

学习了一种新的状压方式,来源于德保

直接依据数码x出现的次数进行状态压缩。比方33211就仅仅须要2*6+1*3+2=17的空间了。利用均值不等式。也差点儿相同仅仅需(ceil(18/10+1)^10)=59049的空间。


并且这样能够用递推来实现。

#include <iostream>
#include <cstdio>
#include <cstring>
#include <algorithm>
#include <cmath>
#include <string>
#include <map>
#include <stack>
#include <vector>
#include <set>
#include <queue>
#define maxn 205
#define MAXN 100005
#define mod 100000000
#define INF 0x3f3f3f3f
#define pi acos(-1.0)
#define eps 1e-6
typedef long long ll;
using namespace std;

ll n,m,ans,tot;
ll dig[10],dp[60000][105];

ll codeit(ll tmp[])  // 状态压缩
{
    ll i,t=0;
    for(i=0; i<10; i++)
    {
        t=t*(dig[i]+1)+tmp[i];
    }
    return t;
}
void decode(ll s,ll tmp[])  // 从压缩状态中解码 还原状态
{
    ll i,t;
    for(i=9; i>=0; i--)
    {
        tmp[i]=s%(dig[i]+1);
        s/=(dig[i]+1);
    }
}
void solve()
{
    ll i,j,t,x=n;
    memset(dig,0,sizeof(dig));
    while(x)
    {
        dig[x%10]++;
        x/=10;
    }
    tot=codeit(dig);
    memset(dp,0,sizeof(dp));
    dp[0][0]=1;
    for(i=0; i<=tot; i++)
    {
        ll cnt[10];
        decode(i,cnt);  // 解码
        for(ll k=0; k<10; k++)  // 枚举下一位
        {
            if(cnt[k]>=dig[k]) continue ;
            if(i==0&&k==0) continue ;
            cnt[k]++;
            ll s=codeit(cnt);  // 状态压缩
            for(j=0; j<m; j++)  // 枚举余数
            {
                dp[s][(j*10+k)%m]+=dp[i][j];  // 转移
            }
            cnt[k]--;
        }
    }
    ans=dp[tot][0];
}
int main()
{
    ll i,j,t;
    while(cin>>n>>m)
    {
        solve();
        cout<<ans<<endl;
    }
    return 0;
}


posted @ 2016-02-03 15:06  mengfanrong  阅读(206)  评论(0编辑  收藏  举报