Codeforces Round #491 (Div. 2) E.Bus Number

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;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值