题意: 给一个数字串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, 除以)即可. 对于除以还有一个疑问: 如果某个情况因为不能整除d而被排除, 会不会让ans除不尽? 其实是不会的. 因为如果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是不会走到重复的排列的.