本文已参与「新人创作礼」活动,一起开启掘金创作之路
L3-020 至多删三个字符 (30 分)
思路:二维DP,dp[i][j]表示取字符串前i个字符的字串,恰好删掉j个字符时,不重复串的个数。
最基本的状态转移方程为dp[i][j] = dp[i-1][j-1] + dp[i-1][j],分别对应删除/不删除第i个字符。但这个状态转移中存在重复:「不删除第i个字符」部分中所有的串都以「第i个字符」作为结尾,而在「删除第i个字符」的部分,有些子串本身以「第i个字符」作为结尾,这两部分子串存在重复。
对此,我们的策略是,在计数dp[i][j]时,子串存在重复时,对于同一字符,「尽量包含较晚在串内出现的字符」。因此,对于上述的情况,我们需要去掉「删除第i个字符」部分中那些重复的子串。
第i个字符上次出现的位置记为prev,在计数dp[i][j]时,「删除第i个字符」的部分中以「第i个字符」作为结尾的子串,他们结尾的字符必定是prev位置的字符。因此,这部分子串的数量为duplicate = dp[prev-1][prev-(i-j)-1],状态转移方程为dp[i][j] = dp[i-1][j-1] + dp[i-1][j] - duplicate
在程序实现时,每种字符上次出现的位置用一个额外的数组记录。时间复杂度为,其中m和k是二维dp的两个维度,m是字符串长度,k是“至多删3个字符”里面的4。
代码
#include <iostream>
#include <cstdio>
#include <cstring>
#include <algorithm>
const int MAX = 1000000;
using namespace std;
int main() {
char s[MAX + 10];
long long dp[MAX + 10][4];
int pos[26];
scanf("%s",s + 1);
dp[0][0] = 1;
int len = strlen(s + 1);
for(int i = 1;i <= len;i ++) {
dp[i][0] = 1;
int d = pos[s[i] - 'a'];
pos[s[i] - 'a'] = i;
for(int j = 1;j < 4;j ++) {
dp[i][j] += dp[i - 1][j - 1] + dp[i - 1][j];
if(d && j - i + d >= 0) {
dp[i][j] -= dp[d - 1][j - i + d];
}
}
}
printf("%lld",dp[len][0] + dp[len][1] + dp[len][2] + dp[len][3]);
}