一. 题目描述
一个序列的子序列是指从原序列中选取部分元素,并保持它们在原序列中的相对顺序所形成的新序列。这意味着在子序列中,元素的相对顺序与它们在原序列中的相对顺序保持一致,但不一定要求是连续的。注意,原序列也可以视为自己的子序列。
现有一个长度为 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;
}