题目大意
给出一个数字$n$,求满足下列条件的数$x$的个数:
- $x<n$
- 对于来自于$x$十进制各个数位上的非零数字,它们的种类与个数都与$n$的相同。
思路
入手点
设$n$有$t$位数字,如果满足上述条件的$x$的位数$t'$比$t$小,那相当于$x$前面含有$t-t'$个前缀0!因此如果我们不考虑第1条,那么$x$就是由$n$各个位上数字包括0组成的全排列。
考虑第一条
我们定义$a_i$为数$n$在第$i$位上的数字,$b_i$则是$x$的。$f(m)$为当$n$与$x$在$m$以上的位数都相等的情况下,满足题目条件的排列个数。若$a_m=b_m$,则转化为子问题$f(m-1)$;如果$b_m<a_m$,则$x$位数比$m$小的那部分无论如何排列,都会满足第一条,故结果为:对于每一个$b_min[0,a_m)$,将其锁定在第$m$位,$n$在$m$及以下的位数的所有数字排除掉被安在第一位的$b_m$后,在$m-1$个数中的全排列。由加法原理,这些结果都要相加。
怎么求含可重元素的全排列
已知一个数组,$h_i$表示一个元素的个数,在长度$m$的区间里求它的全排列。其相当于先在区间中安0,共有$C_m^{h_0}$种选法;然后再在剩余的空间$m-h_0$中看看1的安放方式,即$C_{m-h_0}^{h_1}$。以此类推。根据乘法原理,这些组合数要相乘才为结果。
注意事项
- ans用long long!
- 求组合数递归时不要忘了给C数组赋值!
#include <cstdio> #include <cstring> #include <algorithm> #include <stack> #include <cstdlib> #include <iostream> using namespace std; #define ll long long const int MAX_DIGIT = 55, MAX_NUM = 10; ll A[MAX_DIGIT]; int NumCnt[MAX_NUM]; ll C[MAX_DIGIT][MAX_DIGIT]; ll Comb(int n, int m) { if (n < 0 || m < 0) return 0; if (C[n][m]) return C[n][m]; else if (m == 0 || m == n) return C[n][m] = 1; else if (m == 1) return C[n][m] = n; else if (m > n) return 0; else return C[n][m] = Comb(n - 1, m - 1) + Comb(n - 1, m); } ll GetAns(const int len) { if (len == 1) return 0; ll ans = 0; for (int firstNum = 0; firstNum < A[len]; firstNum++) { NumCnt[firstNum]--; int subLen = len - 1; ll combAns = 1; for (int num = 0; num < 10; num++) { combAns *= Comb(subLen, NumCnt[num]); subLen -= NumCnt[num]; } NumCnt[firstNum]++; ans += combAns; } NumCnt[A[len]]--; ans += GetAns(len - 1); return ans; } int main() { stack<int> st; char c; while (cin >> c && isdigit(c)) st.push(c - '0'); int len = 0; while (!st.empty()) { A[++len] = st.top(); NumCnt[A[len]]++; st.pop(); } cout << GetAns(len) << endl; return 0; }