卡码网 28. 子序列中的K中字母

一. 题目描述

一个序列的子序列是指从原序列中选取部分元素,并保持它们在原序列中的相对顺序所形成的新序列。这意味着在子序列中,元素的相对顺序与它们在原序列中的相对顺序保持一致,但不一定要求是连续的。注意,原序列也可以视为自己的子序列。 

现有一个长度为 n 的仅由小写字母组成的字符串 s,求 s 有多少个子序列恰好包含 k 种字母。

输入描述

输入仅包含一组测试数据。测试数据包含两行: 

第一行包含两个整数 n 和 k(1 ≤ n ≤ 1000,1 ≤ k ≤ 26),用空格隔开,表示字符串的长度和符合要求的子序列的所需长度。

第二行是一个由小写字母组成的字符串,长度为 n。 

输出描述

对于输入的测试数据,输出一个整数,表示计算得到的答案。

输入示例
6 5
eecbad
输出示例
3
提示信息

s有两个子序列"ecbad"满足要求(重复也算),同时s自身也满足要求,所以答案是3; 注意:由于答案会比较大,所以需要将答案对(10^9 + 7)取模以后再输出。

二. 解题思路

三. 代码

#include <iostream>
#include <vector>

#define MOD 1000000007

using namespace std;

long long res = 0;

// x^n mod MOD
long long myPow(long long x, long long n) {
    long long ans = 1;
    while(n > 0) {
        if(n % 2 == 1) {
            ans *= x;
            ans %= MOD;
        }
        x *= x;
        x %= MOD;
        n /= 2;
    }
    return ans;
}

// 搜索,s为每个字母的选择方案数,i为当前索引,letter为每个字母是否出现,k为剩余选择次数,ans为当前方案数
// 从26个字母中选择k个字母,每个字母都有选与不选两个情况,当选择了该字母,方案数需要乘以该字母的选择方案数
void sc (const vector<long long> &s, int i, const vector<bool> &letter, int k, long long ans) {
    if(k == 0) {
        res += ans;
        res %= MOD;
        return;
    }
    if(s.size() - i < k) { return; }
    if(letter[i]) { // 选择该字母,前提是该字母在字符串中存在
        sc(s, i + 1, letter, k - 1, (ans * s[i]) % MOD);
    }
    sc(s, i + 1, letter, k, ans); //不选该字母
}

int main() {
    // 输入
    int n, k;
    cin >> n >> k;
    string s(n, 'a');
    for(int i = 0; i < n; i++) {
        cin >> s[i];
    }

    // 初始化
    vector<long long> numOfLetters(26, 0);
    vector<bool> letter(26, false);

    // 对每一个字母,统计出现次数,并且记录是否出现
    for(int i = 0; i < n; i++) {
        numOfLetters[s[i] - 'a']++;
        letter[s[i] - 'a'] = true;
    }

    // 对于每一个字母,计算出现次数的2次幂减1,即为该字母的选择方案数
    // 例如,一个字母出现了3次,那么选择0个、1个、2个、3个这四种情况总的方案数为2^3-1=7
    // 减一是因为要排除一个都不选的情况
    for(int i = 0; i < 26; i++) {
        if (letter[i]) {
            numOfLetters[i] = myPow(2, numOfLetters[i]) - 1;
        }
    }

    sc(numOfLetters, 0, letter, k, 1);
    cout << res << endl;
    return 0;
}

四. 总结

喜欢的话给个关注吧!!

  • 4
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值