20%:
数字最多有 5 位,可以枚举全排列,判断是否为
m
的倍数。但要注意去重,可以直接数组计数。
时间复杂度:记
40%:(考试的时候貌似全班都这样写的)
可以用 dfs,从前到后考虑每位填什么数字,这样还可以避免出现重复。
时间复杂度上限:
O(10w)
,不过实际上是根本达不到的,因为随着搜索到后面,剩下可选的数字越少。
参考代码:
#include <algorithm>
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <iostream>
using namespace std;
long long n, m;
int len, ans, cnt[11];
void dfs(long long k, int l) {
if (l == len) {
ans += !(k % m);
// if (!(k % m)) printf("%d\n", k);
return;
}
if (l && cnt[0]) {
--cnt[0];
dfs(k * 10LL, l + 1);
++cnt[0];
}
for (int i = 1; i < 10; i++)
if (cnt[i]) {
--cnt[i];
dfs(k * 10LL + i, l + 1);
++cnt[i];
}
}
int main(void) {
freopen("2090.in", "r", stdin);
freopen("2090.out", "w", stdout);
scanf("%lld%lld", &n, &m);
// printf("%I64d %d\n", n, m);
for (; n; n /= 10) ++cnt[n % 10], ++len;
dfs(0LL, 0);
printf("%d\n", ans);
return 0;
}
100:DP。
因为题目限定最终一定是原来的数字,只不过排列不同,我都写出 dfs 了,还没有认识到能“一个一个选”,潜意识更是直接排除掉了“选/不选”这种暗示。
事实上,如果没有
m
的倍数这个限制,可以用组合数学算出答案,也可以用 DP 解决。如果是后者,则会对我们得到正解有帮助。
记
需要注意的是重复的数字和前导 0 需要特判一下,前者可以通过限定相同数字必须按顺序取,后者则在边界条件作规定即可。
这样,时间复杂度为
O(w2×2w)
。
现在的问题中多了一个限制,即最终的数字要是
m
的倍数,其实也不难,但需要有化归的思想。即,
同时我们知道,取模运算是满足许多优美的性质的。
假设已经知道了前
(i−1)
位组成数字对
m
取模的结果,只要乘上 10,加上当前位数字,再对
这不禁启示我们,有限制,那就加上一维。记
f(i,S,j)
为最终数字中的前
i
位,由原数的
这样,用这个三维状态的 DP,加上转移时候需要枚举的
x
,总的时间复杂度为
参考代码:
#include <algorithm>
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <iostream>
using namespace std;
//const int MAXBIT = 20;
//const int MAXM = 100 + 5;
int m;
int num[20];
long long n, f[1 << 19][101];
int bitcount(int stat) { //统计一个数的二进制表示有多少个 1
int c;
for (c = 0; stat; c++) stat = stat & (stat - 1);
return c;
}
int main(void) {
freopen("2090.in", "r", stdin);
freopen("2090.out", "w", stdout);
scanf("%lld%d", &n, &m);
int cnt = 0;
while (n) { //将原数一位位拆分出来
num[cnt++] = n % 10;
n /= 10;
}
sort(num, num + cnt); //排好序之后方便相同数字的特判
int upper_lim = 1 << cnt;
f[0][0] = 1;
for (int i = 0; i < cnt; i++) //已选几位
for (int j = 0; j < upper_lim; j++) //当前集合
if (bitcount(j) == i) //顺推,恰好已选了 i 个数才有可能是当前集合
for (int k = 0; k < m; k++) //当前余数
if (f[j][k]) //有值才住后推,否则没意义
for (int l = 0; l < cnt; l++) //接下来要选原数(排序后)第几位
if ((i || num[l]) && !(j & (1 << l)) && (!l || num[l] != num[l - 1] || (j & (1 << l - 1)))) f[j | (1 << l)][(k * 10 + num[l]) % m] += f[j][k];
//分别对应:首位不能为 0,要被选的数不能出现在当前集合中(即之前不能被选过,否则会重复选), 相同的数要按先后次序取
printf("%lld\n", f[upper_lim - 1][0]);
return 0;
}