题解: 通俗易懂的官方题解。
f [ i ] [ j ] f[i][j] f[i][j]表示前 i i i个字符中长度为 j j j的子序列的个数。
不考虑重复子序列的情况下的状态转移方程:
f [ i ] [ j ] = f [ i 1 ] [ j ] + f [ i 1 ] [ j 1 ] f[i][j]=f[i-1][j]+f[i-1][j-1] f[i][j]=f[i1][j]+f[i1][j1],即不取第 i i i个字符和取第 i i i个字符两种情况。

根据题意:重复的子序列应该是由在 i i i之前的且与 s [ i ] s[i] s[i]相同的字符产生的,根据状态转移方程,考虑 p r e [ s [ i ] ] pre[s[i]] pre[s[i]] s [ i ] s[i] s[i]这个字符在 i i i之前离 i i i最近的位置,那么 f [ p r e [ s [ i ] ] ] [ j ] f[pre[s[i]]][j] f[pre[s[i]]][j]对重复子序列做出的贡献是取了 p r e [ s [ i ] ] pre[s[i]] pre[s[i]]这个位置,那么需要减去的重复子序列就是这部分即 f [ p r e [ s [ i ] ] 1 ] [ j ] f[pre[s[i]]-1][j] f[pre[s[i]]1][j]
那么最终的转移方程就是: f [ i ] [ j ] = f [ i 1 ] [ j ] + f [ i 1 ] [ j 1 ] f [ p r e [ s [ i ] ] 1 ] [ j ] f[i][j]=f[i-1][j]+f[i-1][j-1]-f[pre[s[i]]-1][j] f[i][j]=f[i1][j]+f[i1][j1]f[pre[s[i]]1][j]

状态初始化即 f [ i ] [ 0 ] = 1 f[i][0]=1 f[i][0]=1,对于任意前 i i i个字符,一个也不选也是一种合法方案。


代码:

#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;

typedef long long ll; 
const int N = 1010;
const ll mod = 1e9 + 7;
char s[N];
ll f[N][N];
int n, k, pre[26];

int main()
{
	scanf("%d%d%s", &n, &k, s + 1);
	
	for(int i = 0; i <= n; i++) f[i][0] = 1; //前i个字符中一个都不选也是一种合法方案 
	for(int i = 1; i <= n; i++) {
		int id = s[i] - 'a';
		for(int j = 1; j <= i; j++) {
			f[i][j] = f[i - 1][j] + f[i - 1][j - 1];
			if(pre[id]) f[i][j] -= f[pre[id] - 1][j - 1];
			f[i][j] = (f[i][j] % mod + mod) % mod;
		}
		pre[s[i] - 'a'] = i;
	}
	
	printf("%lld\n", f[n][k]);
}