给出一个数字$n$,求满足下列条件的数$x$的个数:
设$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_m\in[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}$。以此类推。根据乘法原理,这些组合数要相乘才为结果。
#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;
}
原文:https://www.cnblogs.com/headboy2002/p/9147797.html