codeforces401D Roman and Numbers(数位、状压dp)

题目大意:给n和m,求有多少方案,使得重新排列n的数位后能整除m,不能有前导0

(n<01e18,m<0100)

思路:数位dp

从高位到低位进行dp,dp[s][k]表示当前集合s(现在选了那几个数)模m后的余数为k的方案数

时间复杂度O(18*(2^18))

核心转移方程,dp[now][(上次余数*10+a[i])%m] += dp[pre][上次余数]

不过这样算会算重复,比如n=111,可以从101,011,110转移过来,所以最后要除以num[i]! (i出现的次数,i从1到9)

因为要排除前导0的情况,我先算最高位的方案数,这样就把0的方案排除了,比如n=210,最高位我算2,1就好了

注意:我们并不关心集合(状态s)的每个1是第几位,我们只关心上个状态的方案数,比如now = 101,我们只要看001和100的方案数,加上的1就现在处理的a[j](默认放在末尾)

(感觉这样写好理解一点,看了网上代码a[i]==0时候要contine不是很理解QAQ)

(语文不好,表达有点混乱。。。)

#include <iostream>
#include <stdio.h>
#include <cstdio>
#include <stdlib.h>
#include <string>
#include <string.h>
#include <cstring>
#include <math.h>
#include <cmath>
#include <queue>
#include <stack>
#include <vector>
#include <map>
#include <algorithm>
using namespace std;
typedef long long ll;
#define _for(i,a,b) for(int i=(a) ;i<=(b) ;i++)
#define _rep(i,a,b) for(int i=(a) ;i>=(b) ;i--)
#define mst(v,s) memset(v,s,sizeof(v))
#define IOS ios::sync_with_stdio(false)
#define INF 0x7f7f7f7f7f7f7f7f
#define inf 0x7f7f7f7f
#define pb push_back
#define int long long
const int N=3e5+10;
int n,m,k;
int num[10];
int dp[(1<<18)][105];
vector<int > v;
void solve()
{
    n = v.size();
    for(int i=0 ;i<v.size() ;i++)//先算第一位,避免算到前导0
    {
        if(v[i])//非0
        {
            dp[1<<i][v[i]%m]=1;
        }
    }
    for(int i=0 ;i<(1<<n) ;i++)//state
    {
        for(int j=0 ;j<v.size() ;j++)//枚举选哪个
        {
            if( i&(1<<j) )
            for(int k=0 ;k<m ;k++)
            {
                dp[i][(k*10+v[j])%m] += dp[i^(1<<j)][k];//转移后  - j = 转移前
            }
        }
    }
    _for(i,0,9)
    {
        _for(j,1,num[i])
        {
            dp[(1<<n)-1][0] /= j;
        }
    }
    cout<<dp[(1<<n)-1][0]<<endl;
}
signed main()
{
//    freopen("data.txt","r",stdin);
    cin>>n>>m;
    while( n )
    {
        v.pb(n%10);
        num[n%10]++;
        n /= 10;
    }
    solve();
}

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值