給定一個全部由小寫英文字母組成的字符串,允許你至多刪掉其中 3 個字符,結果可能有多少種不同的字符串?
輸入格式:
輸入在一行中給出全部由小寫英文字母組成的、長度在區間 [4, 1] 內的字符串。
輸出格式:
在一行中輸出至多刪掉其中 3 個字符后不同字符串的個數。
輸入樣例:
ababcc
輸出樣例:
25
提示:
刪掉 0 個字符得到 "ababcc"。
刪掉 1 個字符得到 "babcc", "aabcc", "abbcc", "abacc" 和 "ababc"。
刪掉 2 個字符得到 "abcc", "bbcc", "bacc", "babc", "aacc", "aabc", "abbc", "abac" 和 "abab"。
刪掉 3 個字符得到 "abc", "bcc", "acc", "bbc", "bac", "bab", "aac", "aab", "abb" 和 "aba"。
顯然這道題用動態規划狀態轉移,無奈動態規划學的很渣,重復情況需要考慮。
最基本的是到了第i位,刪或者不刪,不刪表示刪除個數不變,刪了表示刪除個數加1,以第i - 1個字符的情況為基礎,創建數組dp[MAX][4],dp[i][j]表示到了第i(i >= 1)個字符,已經刪了j個字符有多少字符串,我們要計算dp[i][j],考慮兩種情況刪或者不刪,刪的話,就是dp[i - 1][j - 1],表示在i - 1時刪了j - 1,到了i后又刪了一個一共刪了j個,不刪的話就是dp[i - 1][j],表示在第i - 1時就刪了j個,第i個不刪所以一共還是刪了j個,dp[i][j] = dp[i - 1][j - 1] + dp[i - 1][j];
但是肯定會有重復的情況,我們只需要保證,到了位置i我們清除掉他的重復情況,這樣在計算后面情況時就不用考慮太多因為前面的都是沒有重復的情況,要去掉重復的,只需要以前面的為基礎即可。
比如 abcdedc,顯然如果刪除了de和ed是一樣的,都生成abcc,我們從前往后計算,到了位置6(下標從1開始),我們按照上面的式子算出了它的情況,其中dp[6][2]會包含刪除ed的情況,dp[6][3]也包含刪除ed但不是ded的情況即在123位置中再選一個刪除,對於dp[6][2]我們需要減去什么,顯然現在是刪除了兩個字符,刪除de的情況其實就是abc一個都不刪除的情況,即dp[3][0],對於dp[6][3]刪除了三個字符,包含ed且不是ded也就是要減去123位置有一個字符被刪除了的情況,即dp[3][1],所以對於dp[i][j],我們需要記錄s[i]上一次出現的位置d d = pos[s[i]],如果d不為0,而且j - (i - d) >= 0(i - d表示什么,比如上例中ed是兩個字符,i - d = 2,j減去它表示到了位置d - 1,刪了幾個字符),那么就dp[i][j] -= dp[d - 1][j - i + d].
代碼:
#include <iostream> #include <cstdio> #include <cstring> #include <algorithm> #define MAX 1000000 using namespace std; typedef long long ll; char s[MAX + 10]; ll dp[MAX + 10][4]; int pos[26]; int main() { 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]); }