E.Bus Number
题意:给定一个数字n代表看到的车牌号,包含n中所有出现过的数字(出现的数字个数小于等于n中出现的数字个数,即为n的子集)且没有前导0的数串都视为合法的车牌号,求所有合法的车牌号个数
题解
首先思考一个问题,如果知道每个数字出现的个数,如何求出满足要求的车牌数,先不考虑前导0,那么实际上就是一个组合的问题,比如长度为n的车牌,第一位可以放n个数,第二位可以放n-1个数,以此类推就是n的阶乘,还要注意去掉重复的情况,即某个数字出现了多次,因此还要除以每种数字出现次数的阶乘。考虑前导0的影响,只需要减去0在第一位的情况数即可,这种情况相当于确定了第一位一定是0,实际上只需要将n和0的个数都减一就和正常情况一样了。
然后我们需要考虑如何找出所有的情况,因为合法的车牌只要包含给定车牌中所有出现过的数字即可。由于合法车牌号一定是给定车牌号的子集,那么我们可以枚举给定车牌号的所有子集,然后筛选出合法的情况进行计算即可。
代码实现
#include <algorithm>
#include <iostream>
#include <stdio.h>
#include <iomanip>
#include <cstring>
#include <string>
#include <vector>
#include <queue>
#include <set>
#include <map>
#define ll long long
using namespace std;
string s;
ll fac[25];
int cnt[10], ct[10];
set<string> st;
void count(string t){ //统计每个数字的个数
memset(cnt, 0, sizeof(cnt));
int len = t.size();
for(int i = 0; i < len; i++) cnt[t[i] - '0']++;
}
ll solve(string t){
count(t);
for(int i = 0; i <= 9; i++){ //如果给定车牌号出现了某个数字而枚举的车牌号没有出现,则不合法
if(ct[i] && !cnt[i]) return 0;
}
sort(t.begin(), t.end()); //排序,去除重复情况,因为只利用了每种数字出现的次数,
if(st.count(t)) return 0; //所以每种数字出现次数一样但位置不同的车牌号算是同一种情况
st.insert(t);
int len = t.size();
ll res = fac[len];
for(int i = 0; i <= 9; i++) res /= fac[cnt[i]];
if(cnt[0]){ //如果出现了0,那么需要去掉前导0的情况
cnt[0]--;
len--;
ll tp = fac[len];
for(int i = 0; i <= 9; i++) tp /= fac[cnt[i]];
res -= tp;
}
return res;
}
int main(){
fac[0] = 1; //预处理阶乘,注意从0开始,否则会出现除以0的情况
for(int i = 1; i <= 20; i++) fac[i] = fac[i - 1] * i;
cin >> s;
ll ans = 0;
int len = s.size();
for(int i = 0; i < len; i++) ct[s[i] - '0']++; //对给定车牌号进行统计,方便之后筛选比对
for(int i = 1; i <= (1 << len); i++){ //枚举子集
string tmp = "";
for(int j = 0; j < len; j++){
if((i >> j) & 1){
tmp += s[j];
}
}
ans += solve(tmp);
}
cout << ans << "\n";
return 0;
}