1. 程式人生 > >NYOJ37、1023、15(迴文串、括號匹配、記憶化搜尋、dp,區間dp)

NYOJ37、1023、15(迴文串、括號匹配、記憶化搜尋、dp,區間dp)

 

迴文字串

時間限制:3000 ms  |  記憶體限制:65535 KB

難度:4

輸入

第一行給出整數N(0<N<100)
接下來的N行,每行一個字串,每個字串長度不超過1000.

輸出

每行輸出所需新增的最少字元數

樣例輸入

1
Ab3bd

樣例輸出

2

描述

所謂迴文字串,就是一個字串,從左到右讀和從右到左讀是完全一樣的,比如"aba"。當然,我們給你的問題不會再簡單到判斷一個字串是不是迴文字串。現在要求你,給你一個字串,可在任意位置新增字元,最少再新增幾個字元,可以使這個字串成為迴文字串。

這題是2000IOI題。

想了半天,終於AC了,開心~~~。

定義狀態 dp[i][j] 表示 區間 i-j 內最少新增的字元個數。

則容易得到狀態轉移方程

dp[i][j] =\left\{\begin{matrix} min(dp[i+1][j],dp[i][j-1]) + 1 \\ dp[i+1][j-1] \qquad s.t\;(a[i] == a[j]) \end{matrix}\right.

即當 a[i] == a[j]  時候,不增加計數,否則增加計數

 

code(記憶化搜尋):

 

#include<iostream>
#include<stdio.h>
#include<vector>
#include<map>
#include<cstring>
#include<cstdlib>
#include<algorithm>
#include<set>
#include<cmath>
using namespace std;
const int N = 1000+5;
int dp[N][N];
char a[N];

int rec(int i, int j){
    if(i >= j)
        return dp[i][j] = 0;
    if(dp[i][j] != -1)
        return dp[i][j];
    int & ans = dp[i][j];
    if(a[i] == a[j])
        ans = rec(i+1,j-1);
    else
        ans = min(rec(i+1,j),rec(i,j-1)) + 1;
    return ans;
}

int main()
    {
            int t;
            scanf("%d",&t);
            while(t--){
                memset(dp,-1,sizeof(dp));
                scanf("%s",a);
                int ans = rec(0,strlen(a)-1);
                printf("%d\n",ans);
            }

        return 0;
    }

看了其他程式碼,似乎還有種方法,是求反轉串與原串最長公共子序列,答案就是原串的長度減去最長公共子串的長度。

和我的思路不大一樣。

 

這題可以用另一種方法,就是把原字串取反,然後和原串求最長公共子串,然後用總長度減去最長公共子串長度,得到的即為結果。

可以理解最長公共子串為不新增字元即滿足條件的字元,其它的需要在對稱位置新增字元。

code2(最長公共子串):

#include<iostream>
#include<stdio.h>
#include<vector>
#include<map>
#include<cstring>
#include<cstdlib>
#include<algorithm>
#include<set>
#include<cmath>
using namespace std;
const int N = 1005;
char s1[N],s2[N];
int dp[N][N];

int main()
    {
        int t,len;
        scanf("%d",&t);
        while(t--){
            memset(dp,0,sizeof(dp));
            scanf("%s",&s1[1]);
            len = strlen(&s1[1]);
            for(int i = len; i > 0; --i){
                s2[i] = s1[len-i+1];
            }
            for(int i = 1; i <= len; ++i){
                for(int j = 1; j <= len; ++j){
                    if(s1[i] == s2[j]){
                        dp[i][j] = dp[i-1][j-1] + 1;
                    }
                    else{
                        dp[i][j] = max(dp[i-1][j],dp[i][j-1]);
                    }
                }
            }
            printf("%d\n",len-dp[len][len]);
        }


        return 0;
    }

 

區間dp思想

嘗試寫的遞推(超時了):

#include<iostream>
#include<cmath>
#include<cstring>
#include<cstdio>
#include<vector>
#include<map>
#include<algorithm>
#include<string>
using namespace std;

const int N = 1000+5;
int dp[N][N];
const int INF = 1 << 30;
char a[N];

int main()
	{
		int t;
		scanf("%d",&t);
		int len;
		while(t--){
			memset(dp,INF,sizeof(dp));
			scanf("%s",a);
			len = strlen(a);
			for(int i = 0; i < len; ++i){
				for(int j = 0; j < len; ++j){
					if(i >= j)
						dp[i][j] = 0;
				}
			}
			bool flag = true;
			for(int i = 0; i < len; ++i){
				if(a[i] != a[len-i-1])
					flag = false;
			}
			if(flag){
				printf("%d\n",0);
				continue;
			}
			for(int l = 2; l <= len; ++l){
				for(int i = 0; i + l <= len; ++i){
					int j = i+l-1;
					for(int k = i; k < j; ++k){
						if(a[i] == a[j]){
							dp[i][j] = dp[i+1][j-1];
						}
						else
							dp[i][j] = min(dp[i+1][j],dp[i][j-1]) + 1;
					}
				}
			}
			printf("%d\n",dp[0][len-1]);
		}

		return 0;
	}

還是迴文

時間限制:2000 ms  |  記憶體限制:65535 KB

難度:3

輸入

多組資料
第一個有兩個數n,m,分別表示字元的種數和字串的長度
第二行給出一串字元,接下來n行,每行有一個字元(a~z)和兩個整數,分別表示新增和刪除這個字元的花費
所有數都不超過2000

輸出

最小花費

樣例輸入

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

樣例輸出

900

描述

判斷迴文串很簡單,把字串變成迴文串也不難。現在我們增加點難度,給出一串字元(全部是小寫字母),新增或刪除一個字元,都會產生一定的花費。那麼,將字串變成迴文串的最小花費是多少呢?

 

這題為什麼這句不被放在 else裡,而是總是執行?

dp[i][j] = min(dp[i+1][j]+cost[s[i]],dp[i][j-1]+cost[s[j]]) \\式1

原因:

例如 求 abaa  可以轉化為 求aba (去除右邊的a) 和 ba (去除兩側的a因為他們想等,可以去除)

但是如果 如果 式1 被放在else裡面了, 就不同時考慮上面 aba 與ba 這兩情況了,前者操作花費為 a, 後者花費為右側側新增b

這顯然是不同的花費,但是如果添加了else,這就只會單獨被考慮一種(取決於 語句順序)。

其實可以理解為,總是要考慮著兩種情況的,只是有一種情況考慮之前需要滿足a[i] == a[j]  所以才加上的if語句。

並不是為了把兩種情況單獨分開考慮。

code1:

注意dp必須要初始化為0  ,這是為了服務於 i >= j 時候 結果為 0 設計的

#include<iostream>
#include<stdio.h>
#include<vector>
#include<map>
#include<cstring>
#include<cstdlib>
#include<algorithm>
#include<set>
#include<cmath>
using namespace std;
const int N = 2000 + 5;
char s[N];
int cost[200];
int dp[N][N];
int main()
    {
        int n,m,x,y;
        char ss[2];
        while(scanf("%d%d",&n,&m) != EOF){
            scanf("%s",s);
            for(int i = 1; i <= n; ++i){
                scanf("%s%d%d",ss,&x,&y);
                cost[ss[0]] = min(x,y);
            }
            memset(dp,0,sizeof(dp));
            for(int l = 2; l <= m; ++l){
                for(int i = 0; i < m-l+1; ++i){
                    int j = i+l-1;
                    dp[i][j] = min(dp[i+1][j]+cost[s[i]],dp[i][j-1]+cost[s[j]]);
                    if(s[i] == s[j])
                        dp[i][j] = min(dp[i+1][j-1],dp[i][j]);
                }
            }
            printf("%d\n",dp[0][m-1]);
        }


        return 0;
    }

另一種遞推式寫法:

兩種遍歷的次序不同,但是異曲同工

           memset(dp,0,sizeof(dp));
            for(int j = 1; j < m; ++j){
                for(int i = j-1; i >= 0; --i){
                    dp[i][j] = min(dp[i+1][j]+cost[s[i]],dp[i][j-1]+cost[s[j]]);
                    if(s[i] == s[j])
                        dp[i][j] = min(dp[i+1][j-1],dp[i][j]);
                }
            }

 

另外、這題用記憶化搜尋超時了(可能是頻繁的memset(dp) 超時?)

code(超時):

#include<iostream>
#include<stdio.h>
#include<vector>
#include<map>
#include<cstring>
#include<cstdlib>
#include<algorithm>
#include<set>
#include<cmath>
using namespace std;
const int N = 200;
//int del[N],add[N];
int cost[N];
char ts[2];
int dp[2000+5][2000+5];
char s[2000+5];
const int INF = 1 << 30;

int rec(int i, int j){
    if(i >= j)
        return 0;
    if(dp[i][j] >= 0)
        return dp[i][j];
    int & ans = dp[i][j];
    ans = min(rec(i+1,j)+cost[s[i]],rec(i,j-1)+cost[s[j]]);
    if(s[i] == s[j])
        ans = rec(i+1,j-1);
        //ans = min(min(rec(i+1,j)+add[s[i]],rec(i+1,j)+del[s[i]]),min(rec(i,j-1)+add[s[j]],rec(i,j-1)+del[s[j]]));
    return ans;
}

int main()
    {
        int n,m;
        char c;

        int x,y;
        while(~scanf("%d%d",&n,&m)){
            memset(dp,-1,sizeof(dp));
            getchar();
            scanf("%s",&s);
            for(int i = 0; i < n; ++i){
                getchar();
                scanf("%s%d%d",&ts,&x,&y);
                cost[ts[0]] = min(x,y);
            }
            printf("%d\n",rec(0,m-1));
        }

        return 0;
    }

題三(括號匹配)

code1(記憶化搜尋)

#include<iostream>
#include<stdio.h>
#include<vector>
#include<map>
#include<cstring>
#include<cstdlib>
#include<algorithm>
#include<set>
#include<cmath>
using namespace std;
const int N = 100+5;
int dp[N][N];
bool vis[N][N];
char s[100+5];

int rec(int i,int j){
    if(i > j)
        return 0;
    if(i == j)
        return 1; // 說明 沒有匹配
    if(vis[i][j])
        return dp[i][j];
    int & ans = dp[i][j];
    if(s[i] == '(' && s[j] == ')' || s[i] == '[' && s[j] == ']')
        ans = min(ans,rec(i+1,j-1));
    if(s[i] == '(' || s[i] == '[')
        ans = min(ans,rec(i+1,j)+1);
    if(s[j] == ')' || s[j] == ']')
        ans = min(ans,rec(i,j-1)+1);
    for(int k = i; k < j; ++k){
        ans = min(ans,rec(i,k)+rec(k+1,j));
    }
    vis[i][j] = true; //求出dp[i][j] 後標記 為 true
    return ans;
}

int main()
    {
        int t;
        scanf("%d",&t);
        while(t--){
            memset(vis,false,sizeof(vis));
            memset(dp,0x3f,sizeof(dp)); //初始化為無窮大
            scanf("%s",s);
            printf("%d\n",rec(0,strlen(s)-1));
        }


        return 0;
    }

注意兩者的 dp 初始化方式不同、但本質一樣、這裡面也是一樣 if語句之間不能加else、每種情況都需要考慮。

code2(遞推):

#include<iostream>
#include<stdio.h>
#include<vector>
#include<map>
#include<cstring>
#include<cstdlib>
#include<algorithm>
#include<set>
#include<cmath>
using namespace std;
const int INF = 0x3f3f3f3f;
const int N = 100+5;
int dp[N][N];
char s[N];

int main()
    {
        int t,len;
        scanf("%d",&t);
        while(t--){
            memset(dp,0,sizeof(dp));
            scanf("%s",s);
            len = strlen(s);
            for(int i = 0; i < len; ++i){
                for(int j = 0; j < len; ++j){
                    if(i == j)
                        dp[i][j] = 1;
                    else if(i > j)
                        dp[i][j] = 0;
                    else
                        dp[i][j] = INF;
                }
            }
            for(int l = 1; l <= len; ++l){
                for(int i = 0; i < len-l+1; ++i){
                    int j = i+l-1;
                    if(s[i] == '(' && s[j] == ')' || s[i] == '[' && s[j] == ']')
                        dp[i][j] = min(dp[i+1][j-1],dp[i][j]);
                    if(s[i] == '(' || s[i] == '[')
                        dp[i][j] = min(dp[i+1][j]+1,dp[i][j]);
                    if(s[j] == ')' || s[j] == ']')
                        dp[i][j] = min(dp[i][j],dp[i][j-1]+1);
                    for(int k = i; k < j; ++k){
                        dp[i][j] = min(dp[i][k]+dp[k+1][j],dp[i][j]);//切分的情況
                    }
                }
            }
            printf("%d\n",dp[0][len-1]);
        }


        return 0;
    }