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

给出数n和m,求n的所有排列中,模m得0的有多少个 n (1 ≤ n < 1018) and m (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);
}


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

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值