首页 > 代码库 > Codeforce 401D Roman and Numbers[数位DP+状态压缩]

Codeforce 401D Roman and Numbers[数位DP+状态压缩]

给出数n和m,求n的所有排列中,模m得0的有多少个 n(1?≤?n?<?1018) andm (1?≤?m?≤?100).

暴力法我们直接枚举n的所有排列,显然18!超时。

考虑怎么dp

假设给了我们数n=23765

显然有

(237%m*10+6)%m=2376%m

(367%m*10+2)%m=3672

我们很自然的想到了

这样的状态转移

dp[i][k]

i代表取的数的状态

代表在取数状态为i的情况下模m为k的数有多少

比如

对于23765的356

取数状态为01011

dp方程就是

dp[i|(1<<j)][(k*10+n[j])%m](j位还没取)+=dp[i][k]

显然,dp[i|(1<<j)][(k*10+n[j])%m]这个状态是多次可达的,所以我们要+=

这里默认是把新数加到前一个状态所有组成的数的末尾,因为加到中间的情况可以由其他状态计算进去

比如

对356来说,现在要加入2

我们加入末尾3562,由上面的柿子算入

如果加到中间,比如成了3256,则这个状态是由325取6得到3256计算


最后,我们这样做是把所有的数字当做互不相同处理的,对相同的数,我们要除以所有重复的情况,即所有相同次数的阶乘的积

#include <iostream>
#include <cstdio>
#include <cstring>
#include <cmath>
#include <algorithm>
using namespace std;
const long long NN=1111;
char str[NN];
long long times[NN];
long long dp[(1<<18)][111];
int main(){
#ifndef ONLINE_JUDGE
	freopen("/home/rainto96/in.txt","r",stdin);
#endif

	long long m;scanf("%s%I64d",str,&m);
	long long len=strlen(str);
	long long all=(1<<len)-1;
	long long left=1;
	for(long long i=0;i<len;i++)
		left*=++times[str[i]-='0'];
        dp[0][0]=1;
	for(long long i=0;i<=all;i++){
		for(long long j=0;j<len;j++){
			if(!(i&(1<<j))){
				if(i || str[j]){
					for(long long k=0;k<=m-1;k++){
						dp[i|(1<<j)][(k*10+str[j])%m]+=dp[i][k];
					}
                                }
			}
		}
	}
	printf("%I64d\n",dp[all][0]/left);
}


Codeforce 401D Roman and Numbers[数位DP+状态压缩]