hdoj 2476 String painter

原題:

 There are two strings A and B with equal length. Both strings are made up of lower case letters. Now you have a powerful string painter. With the help of the painter, you can change a segment of characters of a string to any other character you want. That is, after using the painter, the segment is made up of only one kind of character. Now your task is to change A to B using string painter. What’s the minimum number of operations? 

Input
Input contains multiple cases. Each case consists of two lines:
The first line contains string A.
The second line contains string B.
The length of both strings will not be greater than 100.
Output
A single line contains one integer representing the answer.

Sample Input

zzzzzfzzzzz
abcdefedcba
abababababab
cdcdcdcdcdcd

Sample Output

6
7

中文:
給你兩個字符串a和字符串b,你有一個刷子,一次可以將一個連續區間的字符串刷成同一種字符,限制問你最少刷多少次可以將字符串a刷成b。
例如第一個給的樣例
zzzzzfzzzzz
abcdefedcba
要求將字符串zzzzzfzzzzz刷成abcdefedcba
第一步可以刷成這樣
aaaaaaaaaaa
第二步
abbbbbbbbba
第三步
abcccccccba
第四步
abcdddddcba
第五步
abcdeeedcba
第六步
abcdefedcba

代碼:

#include <bits/stdc++.h>
using namespace std;
const int maxn = 100 + 10;


char s1[maxn],s2[maxn];
int dp[maxn][maxn];
int ans[maxn];

int main()
{
    ios::sync_with_stdio(false);
    while(cin>>&s1[1])
    {
        cin>>&s2[1];
        memset(dp,0,sizeof(dp));
        memset(ans,0,sizeof(ans));
        int len = strlen(&s1[1]);

        for(int R = 1;R<=len ;R++)
        {
            for(int L = R; L>=1; L--)
            {
                dp[L][R] = dp[L+1][R]+1;
                for(int k = L + 1; k<=R ;k++)
                {
                    if(s2[L] == s2[k])
                        dp[L][R] = min(dp[L][R], dp[L+1][k] + dp[k+1][R]);
                }
            }
        }
        for(int i = 1;i<=len;i++)
        {
            ans[i]=dp[1][i];
        }
        for(int i = 1;i<=len;i++)
        {
            if(s1[i] == s2[i])
                ans[i] = ans[i-1];
            else
            {
                for(int j=1;j<=i;j++)
                    ans[i]=min(ans[i],dp[j+1][i] + ans[j]);
            }
        }
        cout<<ans[len]<<endl;
    }
    return 0;
}


解答:

此題目是我做過精妙的區間dp問題之一,看別人代碼看了半天才想明白-_-|| 。
數據上來說,字符串長度最多隻有100。這意味着要求時間複雜度要在O(n^3)以內完成。
按照給的樣例,手動模擬一下不難發現,如果單純利用區間dp的模型來考慮,每次進行狀態轉移的的方法按照區間來考慮,狀態轉移方程如下:
dp[i][j]=min(dp[i][j],dp[i][x1]+paint(x,y)+dp[y+1][j])dp[i][j] = min(dp[i][j],dp[i][x-1] + paint(x,y) + dp[y+1][j] )
其中paint(x,y)表示把a字符串塗成b字符串,這裏有個問題,paint(x,y)函數怎麼實現?
如果要將區間[x,y]塗成字符串b對應的字符串,那麼如何處理這個區間才能使paint函數最優?
這樣又回到了原來的問題。
所以,此題不好在區間上考慮。

考慮將一個空字符串塗抹成字符串b所需要的最少塗抹次數,設置狀態dp[i][j]表示將空字符串區間[i,j]塗抹成字符串b區間[i,j]的字符串所需要的最少塗抹次數:

狀態轉移方程表示爲:
初始dp[i][j]=dp[i+1][j]+1dp[i][j]= dp[i+1][j] + 1表示可以將第i個字符單獨塗成字符串b[i]對應的字符

如果在字符串b中有b[i]==b[k],其中k∈[i+1,j]
dp[i][j]=min(dp[i][j]dp[i+1][k]+dp[k+1][j])dp[i][j] = min(dp[i][j],dp[i+1][k] + dp[k+1][j])
表示如果b[i]字符與b[k]字符相同,那麼可以先將區間[i,k]一次塗抹成字符b[i]的形式

這裏有個問題,如果在b[i]==b[k]b[i]==b[k]dp[i+1][k]dp[i+1][k]表示將區間[i,k]塗抹成字符串b[k],爲什麼不可以表達成下面這樣的形式?
dp[i][j]=min(dp[i][j]dp[i+1][k]+1+dp[k+1][j])dp[i][j] = min(dp[i][j],dp[i+1][k]+1 + dp[k+1][j])

dp[i][j]=min(dp[i][j]dp[i+1][k1]+1+dp[k+1][j])dp[i][j] = min(dp[i][j],dp[i+1][k-1] + 1 + dp[k+1][j])

從代碼中可以看到,首先枚舉右區間,然後再遍歷左區間,這裏右區間是固定的,即使再尋找子狀態來構造當前狀態的最優解也是以最右邊的字符作爲參照標準。
第一個狀態轉移方程不能+1的原因在於,如果b[i]==b[k]b[i]==b[k]那麼你要塗抹區間[i,k]時,也就是在處理第k個字符的時候就已經將整個區間塗抹過了
第二個方程不能寫成dp[i+1][k1]+1dp[i+1][k-1] + 1形式,因爲該形式只表示你考慮第i個字符與第k個字符的塗抹,而沒有將區間表示出來。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章