1. 程式人生 > >[DP總結]字符串DP

[DP總結]字符串DP

絕對值 pen more getchar() soci cos 解決 成功 內容

顧名又思義,是在字符串上進行的DP操作。因為字符串本身可以看作是一個序列,所以有些時候字符串DP可以用區間DP來解決。

P2246 SAC#1 - Hello World(升級版)

題目描述

在講義的某一面,他看見了一篇文章。這篇文章由英文字母(大小寫均有)、數字、和空白字符(制表/空格/回車)構成。

pipapi想起了他最近剛剛學會寫的Hello World程序。他非常好奇,這篇文章中,“HelloWorld”作為子序列到底出現過多少次呢?

由於papapi是個智障,大小寫對於他而言毫無區別;因此,“hEllOWorLD”這樣的子序列也是可以接受的。O和W之間的空格是也是可以少的;也就是說,“HelloWorld”是可以的。根據標程的意思,就是沒有空格,不用考慮空格的情況。

兩個子序列相同當且僅當它們每一個字符所在的位置都相同。

由於答案可能很大,請輸出結果對1000000007(10^9+7)的余數。

輸入輸出格式

輸入格式:

輸入包含若幹行。這些行的內容共同構成一篇文章。

文章以EOF(文件結尾)結束。

輸出格式:

輸出僅包含一個整數,表示這篇文章中“Hello World”出現的次數。 d

輸入輸出樣例

輸入樣例#1:

HhEeLlLlOoWwOoRrLlDd

輸出樣例#1:

1536

輸入樣例#2:

Gou Li Guo Jia Sheng Si Yi
Qi Yin Huo Fu Bi Qu Zhi
River can feed people
Also can race boats
Hall Ellen Ok Words locked 

輸出樣例#2:

273

說明

記n為輸入的文章的長度(字符數)。

對於20%的數據,n <= 20。

對於50%的數據,n <= 500。

對於所有的數據,15 <= n <= 500000。

入門題。設\(dp[i][j]\)表示文本串前i個字符匹配helloworld模板的前j個字符的匹配數。顯然當\(a[i]=b[j]\)時有\(dp[i][j]=dp[i-1][j-1] + dp[i-1][j]\),其他情況\(dp[i][j]=dp[i-1][j]\)。前面一維直接滾動優化掉。

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

typedef long long LL;
const int MOD = 1e9 + 7;
char ch1[233] = "#helloworld";
char ch2[233] = "#HELLOWORLD";
int f[233];

int main() {
    char x; f[0] = 1;
    while ((x = getchar())!=EOF) 
        for (int i = 10; i >= 1; -- i) 
            if (x == ch1[i] || x == ch2[i]) 
                f[i] = (f[i-1] + f[i]) % MOD;
    cout << f[10] << endl;
    return 0;
}

P2890 [USACO07OPEN]便宜的回文Cheapest Palindrome

題目描述

Keeping track of all the cows can be a tricky task so Farmer John has installed a system to automate it. He has installed on each cow an electronic ID tag that the system will read as the cows pass by a scanner. Each ID tag‘s contents are currently a single string with length M (1 ≤ M ≤ 2,000) characters drawn from an alphabet of N (1 ≤ N ≤ 26) different symbols (namely, the lower-case roman alphabet).

Cows, being the mischievous creatures they are, sometimes try to spoof the system by walking backwards. While a cow whose ID is "abcba" would read the same no matter which direction the she walks, a cow with the ID "abcb" can potentially register as two different IDs ("abcb" and "bcba").

FJ would like to change the cows‘s ID tags so they read the same no matter which direction the cow walks by. For example, "abcb" can be changed by adding "a" at the end to form "abcba" so that the ID is palindromic (reads the same forwards and backwards). Some other ways to change the ID to be palindromic are include adding the three letters "bcb" to the begining to yield the ID "bcbabcb" or removing the letter "a" to yield the ID "bcb". One can add or remove characters at any location in the string yielding a string longer or shorter than the original string.

Unfortunately as the ID tags are electronic, each character insertion or deletion has a cost (0 ≤ cost ≤ 10,000) which varies depending on exactly which character value to be added or deleted. Given the content of a cow‘s ID tag and the cost of inserting or deleting each of the alphabet‘s characters, find the minimum cost to change the ID tag so it satisfies FJ‘s requirements. An empty ID tag is considered to satisfy the requirements of reading the same forward and backward. Only letters with associated costs can be added to a string.

字串S長M,由N個小寫字母構成。欲通過增刪字母將其變為回文串,增刪特定字母花費不同,求最小花費。

輸入輸出格式

輸入格式:

Line 1: Two space-separated integers: N and M

Line 2: This line contains exactly M characters which constitute the initial ID string

Lines 3..N+2: Each line contains three space-separated entities: a character of the input alphabet and two integers which are respectively the cost of adding and deleting that character.

輸出格式:

Line 1: A single line with a single integer that is the minimum cost to change the given name tag.

輸入輸出樣例

輸入樣例#1:

3 4
abcb
a 1000 1100
b 350 700
c 200 800

輸出樣例#1:

900

說明

If we insert an "a" on the end to get "abcba", the cost would be 1000. If we delete the "a" on the beginning to get "bcb", the cost would be 1100. If we insert "bcb" at the begining of the string, the cost would be 350 + 200 + 350 = 900, which is the minimum.

比較經典的字符串DP了。設\(dp[i][j]\)表示區間[i,j]變成回文串的最小花費,需要用到區間DP的思想。考慮如何用一個小區間更新一個大區間。如果大區間是\(dp[i][j]\),若s[i]=s[j],那麽\(dp[i][j]=dp[i+1][j-1]\),即當前大區間可以由去掉其兩端的小區間更新而來而不用花費。不等的時候,\(dp[i][j]=min(dp[i+1][j]+min(add[s[i]],del[s[i]]),dp[i][j-1]+min(add[s[j],del[s[j]]])\)

#include<iostream>
#include<algorithm>
using namespace std;

const int M = 2005, N = 256;
int n, m;
char c, s[M];
int del[N], add[N], f[M][M];

int main() {
    scanf("%d%d%s", &n, &m, (s+1));
    for (int i = 1; i <= n; ++ i) {
        cin >> c; 
        cin >> add[c] >> del[c];
    } 
    for (int L = 2; L <= m; ++ L) 
        for (int i = 1; i + L - 1 <= m; ++ i) {
            int j = i + L - 1;
            if (s[i] == s[j]) f[i][j] = f[i + 1][j - 1];
            else f[i][j] = min(f[i+1][j] + min(add[s[i]], del[s[i]]),
                               f[i][j-1] + min(add[s[j]], del[s[j]]));
        }
    cout<<f[1][m]<<endl;
    return 0;
}

P1279 字串距離

題目描述

設有字符串X,我們稱在X的頭尾及中間插入任意多個空格後構成的新字符串為X的擴展串,如字符串X為”abcbcd”,則字符串“abcb□cd”,“□a□bcbcd□”和“abcb□cd□”都是X的擴展串,這裏“□”代表空格字符。

如果A1是字符串A的擴展串,B1是字符串B的擴展串,A1與B1具有相同的長度,那麽我捫定義字符串A1與B1的距離為相應位置上的字符的距離總和,而兩個非空格字符的距離定義為它們的ASCII碼的差的絕對值,而空格字符與其他任意字符之間的距離為已知的定值K,空格字符與空格字符的距離為0。在字符串A、B的所有擴展串中,必定存在兩個等長的擴展串A1、B1,使得A1與B1之間的距離達到最小,我們將這一距離定義為字符串A、B的距離。

請你寫一個程序,求出字符串A、B的距離。

輸入輸出格式

輸入格式:

輸入文件第一行為字符串A,第二行為字符串B。A、B均由小寫字母組成且長度均不超過2000。第三行為一個整數K(1≤K≤100),表示空格與其他字符的距離。

輸出格式:

輸出文件僅一行包含一個整數,表示所求得字符串A、B的距離。

輸入輸出樣例

輸入樣例#1

cmc
snmn
2

輸出樣例#1:

10

\(dp[i][j]\)表示第一個串的前i個字符和第二個串的前j個字符的最優值,兩個空格對應顯然沒有意義,那麽有3種轉移:\(dp[i-1][j]+K\)\(dp[i][j-1]+K\)\(dp[i-1][j-1]+abs(S1[i]-S2[j])\),分別表示S1[i]與空格匹配,S2[j]與空格匹配,S1[i]與S2[j]匹配。

#include <cmath>
#include <cstdio>
#include <cstring>
#include <cstdlib>
#include <cctype>
#include <map>
#include <queue>
#include <vector>
#include <iostream>
#include <algorithm>
using namespace std;

const int MAXN = 2010;
int K;
char S1[MAXN], S2[MAXN];
int dp[MAXN][MAXN];

int clac(int i, int j) {
    return abs((int) (S1[i] - 'a') - (int) (S2[j] - 'a'));
}

int main( ) {
    scanf("%s%s%d", S1 + 1, S2 + 1, &K);
    int len1 = strlen(S1 + 1), len2 = strlen(S2 + 1); 
    memset(dp, 63, sizeof(dp));
    dp[0][0] = 0;
    for (int i = 0; i <= len1; ++ i)
        for (int j = 0; j <= len2; ++ j) {
            if (i) dp[i][j] = min(dp[i][j], dp[i - 1][j] + K);
            if (j) dp[i][j] = min(dp[i][j], dp[i][j - 1] + K);
            if (i && j)  
                dp[i][j] = min(dp[i][j], dp[i - 1][j - 1] + clac(i, j));
        }
    printf("%d\n", dp[len1][len2]);
    return 0;
}

caioj 1061: [視頻]背包7(匹配性填滿型 完全 背包)

時間限制: 1 Sec 內存限制: 128 MB

【問題描述】
判斷句子是否可以被劃分成若幹單詞,這些單詞只可以 “one”、“puton”、“out”、“output”、“in”和“input”。
輸入n個字符串,長度不超過1000000,表示一句句子。
如果可能是那兩個人的對話,則輸出“YES”;否則,輸出“NO”。
【輸入文件】
第一行一個整數n,表示一共有n句句子。
此後每行一個字符串,表示一句句子。
【輸出文件】
n行,每行一個“YES”或“NO”,表示你的判斷結果。

樣例輸入輸出

樣例輸入

6
puton
inonputin
oneputonininputoutoutput
oneininputwooutoutput
outpu
utput

樣例輸出

YES
NO
YES
NO
NO
NO

如果不知道這是一道背包題的話,可能沒幾個人會往背包的方面想。我們不妨把題目中給定的6個單詞看做六個數量無限的物品,現在他們要裝到一個背包中,比如要裝一個input,能裝入背包的條件是當前裝了一些的背包中,再往後需要的字母依次是i,n,p,u,t。最後成功的條件是背包被裝滿,即\(dp[串長]\)有值。\(dp[i]\)表示前i個字符是否能完成匹配。如上所述,則dp[i]能由dp[i - len[i]] 推出,當且僅當,子串c[j - len[i] ~ j] 為給定的單詞。

單純這樣做還會TLE。可以簡單優化一下,具體參照代碼。

#include <cmath>
#include <cstdio>
#include <cstring>
#include <cstdlib>
#include <cctype>
#include <map>
#include <queue>
#include <vector>
#include <iostream>
#include <algorithm>
using namespace std;

const int MAXM = 1000010;
const int MAXN = 1010;
int n;
string c[] = {"", "one", "puton", "out", "output", "in", "input"};
int len[] = {0, 3, 5, 3, 6, 2, 5};
int dp[MAXM];

int main( ) {
    scanf("%d", &n);
    string s; int l;
    for (int i = 1; i <= n; ++ i) {
        memset(dp, 0, sizeof(dp));
        cin >> s; 
        l = s.size( );
        dp[0] = 1;
        for (int j = 1; j <= l; ++ j)
        if(s[j - 1] == 'e' || s[j - 1] == 'n' || s[j - 1] == 't') //優化
            for (int i = 1; i <= 6; ++ i) 
                if (j - len[i] >= 0) 
                if(s[j - len[i]] == 'o' || s[j - len[i]] == 'p' || s[j - len[i]] == 'i') //優化
                    if (s.substr(j - len[i], len[i]) == c[i])
                        dp[j] = dp[j] | dp[j - len[i]];
        if (dp[l]) printf("YES\n");
        else printf("NO\n");
    }
}

P1136 迎接儀式

題目描述

LHX教主要來X市指導OI學習工作了。為了迎接教主,在一條道路旁,一群Orz教主er穿著文化衫站在道路兩旁迎接教主,每件文化衫上都印著大字。一旁的Orzer依次擺出“歡迎歡迎歡迎歡迎……”的大字,但是領隊突然發現,另一旁穿著“教”和“主”字文化衫的Orzer卻不太和諧。

為了簡單描述這個不和諧的隊列,我們用“j”替代“教”,“z”替代“主”。而一個“j”與“z”組成的序列則可以描述當前的隊列。為了讓教主看得盡量舒服,你必須調整隊列,使得“jz”子串盡量多。每次調整你可以交換任意位置上的兩個人,也就是序列中任意位置上的兩個字母。而因為教主馬上就來了,時間僅夠最多作K次調整(當然可以調整不滿K次),所以這個問題交給了你。

輸入輸出格式

輸入格式:

第一行包含2個正整數N與K,表示了序列長度與最多交換次數。

第二行包含了一個長度為N的字符串,字符串僅由字母“j”與字母“z”組成,描述了這個序列。

輸出格式:

一個非負整數,為調整最多K次後最後最多能出現多少個“jz”子串。

輸入輸出樣例

輸入樣例#1:

5 2 
zzzjj

輸出樣例#1:

2

說明

【樣例說明】

第1次交換位置1上的z和位置4上的j,變為jzzzj;

第2次交換位置4上的z和位置5上的j,變為jzzjz。

最後的串有2個“jz”子串。

【數據規模與約定】

對於10%的數據,有N≤10;

對於30%的數據,有K≤10;

對於40%的數據,有N≤50;

對於100%的數據,有N≤500,K≤100。

一開始不知道怎麽做。考慮\(dp[i][j][k]\)表示考慮前i個字符,有j個j變成了z,k個z變成了j

然後呢?

然後我就不知道了

首先顯然兩個一樣的字符不會被修改。

相鄰兩字符有四種情況可以轉移:zj jz jj zz

\(s[i]='j' ~\&\&~ s[i-1]='z'\)\(dp[i][j][k]=max(dp[i][j][k],dp[i-2][j][k]+1);\)

\(s[i]='z' ~\&\&~ s[i-1]='j'\)\(dp[i][j][k]=max(dp[i][j][k],dp[i-2][j-1][k-1]+1);\)

上面兩種情況都比較顯然。

那麽另外兩種情況呢?

\(s[i]='j' ~\&\&~ s[i-1]='j'\)\(dp[i][j][k]=max(dp[i][j][k],dp[i-2][j-1][k]+1);\)

\(s[i]='z' ~\&\&~ s[i-1]='z'\)\(dp[i][j][k]=max(dp[i][j][k],dp[i-2][j][k-1]+1);\)

感性理解一下就是,我把不合法的變成合法的,可以看做當前不合法的與前面或後面某個數交換了一下,使之合法,但具體是與哪個數交換的,我們不需要去知道。因為當交換次數\(j=k\)時,顯然是存在至少一種合法的操作順序,令原字符串可以通過j=k次交換變成合法。當交換次數\(j \not= k\)時,顯然不存在這種交換方式,那我們就不必去管他。

最後對所有的\(dp[N][i][i]\)取max。

比較巧妙。

順便,註意邊界處理。

#include <algorithm>
#include <cctype>
#include <cmath>
#include <cstring>
#include <cstdlib>
#include <cstdio>
#include <ctime>
#include <iostream>
#include <map>
#include <queue>
#include <vector>
using namespace std;

const int INF = 0x3f3f3f3f;
const int MAXN = 510;
const int MAXK = 110;
int N, K;
//dp[i][j][k]表示考慮前i個字符,有j個‘j’變成了z,k個‘z’變成了j
int l[MAXN], dp[MAXN][MAXK][MAXK];
char tmp[MAXN];

template <typename _Tp>
inline void read(_Tp &x) {
    char ch = getchar( ); bool f = 0; x = 0;
    while (!isdigit(ch)) { if (ch == '-') f = 1; ch = getchar( ); }
    while (isdigit(ch)) x = x * 10 + ch - '0', ch = getchar( );
    x = f ? -x : x;
}

int main( ) {
    memset(dp, ~63, sizeof(dp));
    read(N), read(K);
    scanf("%s", tmp + 1);
    for (int i = 1; i <= N; ++ i) 
        if (tmp[i] == 'z') l[i] = 1;
        else l[i] = 0;
    //for (int i = 1; i <= N; ++ i) printf("%d\n", l[i]);
    dp[0][0][0] = dp[1][0][0] = dp[1][l[1]][l[1]] = 0;
    for (int i = 2; i <= N; ++ i) 
        for (int j = 0; j <= K; ++ j)
            for (int k = 0; k <= K; ++ k) {
                dp[i][j][k] = dp[i - 1][j][k];
                if (!l[i - 1] && l[i]) 
                    dp[i][j][k] = max(dp[i][j][k], dp[i - 2][j][k] + 1);
                if (k && l[i] && l[i - 1])
                    dp[i][j][k] = max(dp[i][j][k], dp[i - 2][j][k - 1] + 1);
                if (j && !l[i] && !l[i - 1])
                    dp[i][j][k] = max(dp[i][j][k], dp[i - 2][j - 1][k] + 1);
                if (j && k && !l[i] && l[i - 1]) 
                    dp[i][j][k] = max(dp[i][j][k], dp[i - 2][j - 1][k - 1] + 1);
            }
    int ans = 0;
    for (int i = 0; i <= K; ++ i) ans = max(ans, dp[N][i][i]);
    printf("%d\n", ans);
    return 0; 
}

[DP總結]字符串DP