题目链接: https://codeforces.com/problemset/problem/991/E
题意:
给你一个数字字符串, 长度不超过18位, 问多少个序列满足:
- 字符串中所有出现过的数字在序列中至少出现一次
- 字符串的某一数字的出现次数要大于等于序列中该数字出现的次数
- 序列不能是 0 开头 (公交车牌号肯定不是0开头= =)
思路: 数字字符串的长度不超过18位, 可以考虑搜索出所有的数字组合情况, 然后求当前数字组合情况的方案数, 方案数可以先包含 0 开头的情况, 然后减去 0 开头的情况.
举个例子: 如果当前数字组合情况为: 0022569
那么当前的方案数为 C 7 2 ∗ C 5 2 ∗ C 3 1 ∗ C 2 1 ∗ C 1 1 C_7^2 * C_5^2 * C_3^1 * C_2^1 * C_1^1 C72∗C52∗C31∗C21∗C11, 那么 0 开头的方案数为 C 6 1 ∗ C 5 2 ∗ C 3 1 ∗ C 2 1 ∗ C 1 1 C_6^1 * C_5^2 * C_3^1 * C_2^1 * C_1^1 C61∗C52∗C31∗C21∗C11, 前者减去后者则为当前组合的方案数
AC代码
public class Main {
public static final int M = 200;
public static int mod = Integer.MAX_VALUE;
public static long[] invFact = new long[M];
public static long[] fact = new long[M];
public static int[]num = new int[12];
public static int[]tmp = new int[12];
public static long ans = 0;
public static void main(String[] args) {
InputReader in = new InputReader();
getInvArray();
String s = in.next();
Arrays.fill(num, 0);
Arrays.fill(tmp, 0);
for(int i = 0; i < s.length(); i++) {
num[s.charAt(i) - '0']++;
}
dfs(0, 0);
System.out.println(ans);
}
public static void dfs(int x, int count) {
if(x == 10) {
// 不考虑前导为0
int tmpCount = count;
long tmpp = 1;
for(int i = 0; i < 10; i++) {
if(tmp[i] > 0) {
tmpp *= C(tmpCount, tmp[i]);
tmpCount -= tmp[i];
}
}
// 减去前导为0的情况
// if(tmp[0] > 0) tmpp -= tmpp * tmp[0] / count;
ans += tmpp;
// 减去前导为0的情况
if(tmp[0] > 0) {
tmpp = 1;
count--;
for(int i = 0; i < 10; i++) {
if(tmp[i] > 0) {
tmpp *= C(count, i == 0 ? tmp[i] - 1 : tmp[i]);
count -= i == 0 ? tmp[i] - 1 : tmp[i];
}
}
ans -= tmpp;
}
return ;
}
if(num[x] == 0) {
dfs(x + 1, count);
return ;
}
for(int i = 1; i <= num[x]; i++) {
tmp[x] = i;
dfs(x + 1, count + i);
}
}
public static long C(int n, int m) {
return (fact[n] * invFact[m] % mod * invFact[n - m] % mod);
}
public static long ksm(long n, long m) {
long ans = 1L;
while(m > 0) {
if((m & 1) == 1) {
ans *= n;
ans %= mod;
}
n = n * n % mod;
n %= mod;
m >>= 1;
}
return ans % mod;
}
public static long getInv(long x) {
return ksm(x, (mod - 2) * 1L);
}
public static void getInvArray() {
fact[0] = 1L;
invFact[1] = invFact[0] = 1L;
for(int i = 1; i < M; i++) {
fact[i] = i * fact[i - 1] % mod;
}
for(int i = 2; i < M; i++) {
invFact[i] = invFact[i - 1] * getInv(i * 1L) % mod; // 阶乘逆元
}
}
}
class InputReader {
BufferedReader buf;
StringTokenizer tok;
InputReader() {
buf = new BufferedReader(new InputStreamReader(System.in));
}
boolean hasNext() {
while(tok == null || !tok.hasMoreElements()) {
try {
tok = new StringTokenizer(buf.readLine());
} catch (Exception e) {
return false;
}
}
return true;
}
String next() {
if(hasNext()) return tok.nextToken();
return null;
}
}