洛谷P4163 排列 数位dp / 全排列

题意: 给一个数字串s和正整数d, 统计s有多少种不同的排列能被d整除(可以有前导0)。

思路:

next_permutation直接秒, 然而我用了数位dp...

因为s最多只有10位, d只有1000, 所以dp用[10]记录pos到第几位, [2]*10记录某一位有没有用到, [1000]记录当前%d余数.

因为记录某位置有没有用过, 这样做之后会有重复, 类似000会被记为6种. 可以统计s中每个数字出现的次数, 然后dp的答案除以每个次数的阶乘(例如223算完假如是6, 除以A_{2}^{2})即可. 对于除以A_{x}^{x}还有一个疑问: 如果某个情况因为不能整除d而被排除, 会不会让ans除不尽A_{x}^{x}? 其实是不会的. 因为如果X排列不能除尽, 那么它的重复排列X'当然也不能除尽, 毕竟他们本质上是同一个, 只不过是因为算法问题产生了重复.

代码:

#include<bits/stdc++.h>

using namespace std;

void debug_out() {
    cerr << '\n';
}

template<typename T, typename ...R>
void debug_out(const T &f, const R &...r) {
    cerr << f << " ";
    debug_out(r...);
}

#define debug(...) cerr << "[" << #__VA_ARGS__ << "]: ", debug_out(__VA_ARGS__);

typedef long long ll;

const int M = 2e5 + 5;
const int inf = 1e9 + 5;
const int mod = 1e9 + 7;

int _;
char a[15];
int d;
int ccnt;

void init() {
    scanf("%s", a);
    ccnt = strlen(a);
    scanf("%d", &d);
}

int dp[10][2][2][2][2][2][2][2][2][2][2][1000];
//         0  1  2  3  4  5  6  7  8  9位置

int dfs(int pos, bool hv[], int res) {
    if (pos == ccnt) {
        //数位dp的dfs结束在数组结尾的下一位.
        if (res == 0)return 1;
        return 0;
    }

    if (dp[pos][hv[0]][hv[1]][hv[2]][hv[3]][hv[4]][hv[5]][hv[6]][hv[7]][hv[8]][hv[9]][res] != -1) {
        return dp[pos][hv[0]][hv[1]][hv[2]][hv[3]][hv[4]][hv[5]][hv[6]][hv[7]][hv[8]][hv[9]][res];
    }
    int ans = 0;
    for (int i = 0; i < ccnt; i++) {
        //hv数组记录每个位置的数字有没有被用过
        if (hv[i] == 0) {
            //如果没有被用过就把在下一位用一下, dfs
            hv[i] = 1;
            ans += dfs(pos + 1, hv, (res * 10 + (a[i] - '0')) % d);
            hv[i]=0;
            //dfs后, 再改回来
        }
    }
    dp[pos][hv[0]][hv[1]][hv[2]][hv[3]][hv[4]][hv[5]][hv[6]][hv[7]][hv[8]][hv[9]][res] = ans;
    return ans;
}

int Jc[15];

void solve() {
    memset(dp, -1, sizeof(dp));
    bool v[10]={0};
    int ans = dfs(0, v, 0);
    int c[15] = {0};//0~9每个数字出现的次数
    for (int i = 0; i < ccnt; i++)c[a[i] - '0']++;
    for (int i = 0; i <= 9; i++)ans /= Jc[c[i]];

    printf("%d\n", ans);

}

int main() {
    scanf("%d", &_);
    while (_--) {
        init();
        Jc[0] = 1;
        for (int i = 1; i <= 10; i++) {//阶乘
            Jc[i] = Jc[i - 1] * i;
        }
        solve();

    }

    return 0;
}

全排列(next_permutation)的代码就不贴了.

要注意, next_permutation是不会走到重复的排列的.

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值