NYOJ37、1023、15(迴文串、括號匹配、記憶化搜尋、dp,區間dp)
迴文字串
時間限制:3000 ms | 記憶體限制:65535 KB
難度:4
輸入
第一行給出整數N(0<N<100)
接下來的N行,每行一個字串,每個字串長度不超過1000.
輸出
每行輸出所需新增的最少字元數
樣例輸入
1
Ab3bd
樣例輸出
2
描述
所謂迴文字串,就是一個字串,從左到右讀和從右到左讀是完全一樣的,比如"aba"。當然,我們給你的問題不會再簡單到判斷一個字串是不是迴文字串。現在要求你,給你一個字串,可在任意位置新增字元,最少再新增幾個字元,可以使這個字串成為迴文字串。
這題是2000IOI題。
想了半天,終於AC了,開心~~~。
定義狀態 表示 區間 內最少新增的字元個數。
則容易得到狀態轉移方程
即當 時候,不增加計數,否則增加計數
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
原因:
例如 求 可以轉化為 求 (去除右邊的a) 和 (去除兩側的a因為他們想等,可以去除)
但是如果 如果 式1 被放在else裡面了, 就不同時考慮上面 與 這兩情況了,前者操作花費為 a, 後者花費為右側側新增b
這顯然是不同的花費,但是如果添加了else,這就只會單獨被考慮一種(取決於 語句順序)。
其實可以理解為,總是要考慮著兩種情況的,只是有一種情況考慮之前需要滿足 所以才加上的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;
}