题目大意
给一个字符串问这个字符串k个不同子串和的最小值。
解题思路
很明显算出来空串的个数,长度为1的不同子串的个数,长度为2的不同子串的个数...长度为n的不同子串的个数,然后就可以计算出答案了。
长度为m的不同子串,即从n个字符里头选m个字符的不同选法,即C(n, m),等等,如果字符串是ababab,那么长度为2的不同子串数是C(6, 2)吗?显然不是。考虑"abab"计算长度为2的子串,在第一个b的时候计算了ba,在第二个b的时候计算了三个ba,但实际上应该算成一个。我们发现当像个字符相同的时候,左边那个字符之前的方案和这两个字符中的任意一个组合都是重复的方案,所以我们每次在计算时减去前一个字符构成的方案即可。
我们设dp[i][j]表示前i个字符里选j个的方案,可以用组合数的状态转移方程,dp[i][j] = dp[i-1][j-1]+dp[i-1][j](选第i个,剩下的i-1里选j-1个 or 不选第i个,剩下的i-1个里面选j个)。然后减去重复的,也就是前一个字符str[i]所在的方案数dp[pre[str[i]]-1]j-1。
代码
const int maxn = 1e2+10;
ll dp[maxn][maxn];
char str[maxn]; int pre[200];
int main() {
IOS;
int n; ll k; cin >> n >> k;
cin >> str+1;
for (int i = 0; i<=n; ++i) dp[i][0] = 1;
for (int i = 1; i<=n; ++i) {
for (int j = 1; j<=i; ++j) {
dp[i][j] += dp[i-1][j-1]+dp[i-1][j];
if (pre[str[i]]) dp[i][j] -= dp[pre[str[i]]-1][j-1];
if (dp[i][j]>k) dp[i][j] = k;
}
pre[str[i]] = i;
}
ll sum = 0;
for (int i = n; i>=0; --i) {
if (k>dp[n][i]) {
k -= dp[n][i];
sum += dp[n][i]*(n-i);
}
else {
sum += 1LL*k*(n-i);
k = 0;
}
}
if (k>0) sum = -1;
cout << sum << endl;
return 0;
}