首页 > 代码库 > [Codefoces 401D]Roman and Numbers 数位dp

[Codefoces 401D]Roman and Numbers 数位dp

http://codeforces.com/problemset/problem/401/D

题目大意:给定一个数字n,将n的每一位数字重新排列,求在这些排列数之中可以被n整除的方法数。

解题思路:

暴力超时……

大多数人的写法是进行位压缩,不过那样的话需要2^18*100 的空间,效率比较低,重复状态数较多,处理起来也不方便,这一题是给出了512M的空间。但是如果空间再小一倍,前者的方法就无能为力了。

发现有一种对于数位dp来说比较好的状态压缩方式,直接根据数码x出现的次数进行状态压缩。比如说333444,如果用前者的方法压缩就需要2^6=64的空间,而直接按照出现次数压缩就只需要3*3的空间,对于极限数据,利用均值不等式,也差不多只需(ceil(18/10+1)^10)=59049的空间,提高了空间的利用率(原来是2^18=262144)与判重的效率。

dp方程也很容易建立对于编码i所有对n取余得j的数字 其余数乘10加上下一位数字得到下一种状态的一部分。结果就是全部数字都使用的余为0的情况(dp[s][n])。

这种压缩的关键在于codeit和decode函数

code

#include <cstdio>
#include <cstring>
#include <cmath>
#include <map>
#define SIZES 40000
using namespace std;
char n[20];
int c[10],d[10],tim[10],m;
long long dp[60000][100];
int len;
int codeit(int *te)
{
    int res=0;
    for (int i=0;i<10;i++)
        res=res*(tim[i]+1)+te[i];
    return res;
}
long long decode(int l,int *t)
{
    for (int i=9;i>=0;i--){
        t[i]=(l%(tim[i]+1));
        l/=(tim[i]+1);
        }
}
int main()
{
    int s;
    while(~scanf("%s%I64d",n,&m)){
    memset(dp,0,sizeof dp);
    len=strlen(n);
    memset(tim,0,sizeof(tim));
    for (int i=0;i<len;i++)
        ++tim[n[i]-'0'];
    int s=codeit(tim);
//    printf("%d\n",s);
    dp[0][0]=1;
    for (int i=0;i<s;i++){
        int t[10];
        decode(i,t);
        for (int j=0;j<10;j++)
            if (i+j)
            if (tim[j]-t[j]){
            t[j]++;
            int pt=codeit(t);
            for (int k=0;k<m;k++)
            dp[pt][(k*10+j)%m]+=dp[i][k];
            t[j]--;
            }
        }
    printf("%I64d\n",dp[s][0]);
    }
}