字串dp 核心[i,j] (1)表示 左邊端點為i 右邊端點為j(區間合併) 有時候for k切割區間 (2)表示i為第一個串第i個位置 j為第二個串第j個位置 (3).
阿新 • • 發佈:2022-04-01
串問題
最短編輯距離https://www.acwing.com/problem/content/904/
f[i][j] i表示a的第i個位置 j表示b的第j個位置 讓從原序列 a[i]和b[j]開始 讓他們相等的最小操作
輸入兩個字串 求讓a和b相等的最小操作
1)刪除操作:把a[i]刪掉之後 a[1i]和b[1j]匹配
說明之前已經讓a[1(i-1)]和b[1j]匹配 情況+1
f[i-1][j] + 1
2)插入操作:對a插入一個 之後a[i]與b[j]完全匹配,所以插入的就是b[j]
那說明之前a[1i]和b[1(j-1)]匹配
f[i][j-1] + 1
3)替換操作:把a[i]改成b[j]之後想要a[1i]與b[1
那麼修改這一位之前,a[1(i-1)]應該與b[1(j-1)]匹配
f[i-1][j-1] + 1
但是如果本來a[i]與b[j]這一位上就相等,那麼不用改,即
f[i-1][j-1] + 0
#include <iostream> #include <algorithm> using namespace std; const int N = 1010; int n, m; char a[N], b[N]; int f[N][N]; int main() { scanf("%d%s", &n, a + 1); scanf("%d%s", &m, b + 1); for (int i = 0; i <= m; i ++ ) f[0][i] = i;//a初始長度0 b長度i i多長增加多少次 for (int i = 0; i <= n; i ++ ) f[i][0] = i; for (int i = 1; i <= n; i ++ ) for (int j = 1; j <= m; j ++ ) { f[i][j] = min(f[i - 1][j] + 1, f[i][j - 1] + 1);//先寫必然滿足的情況 在a[i-1]和b[j]相等的時候 只需要 a[i-1]補一個就行了 if (a[i] == b[j]) f[i][j] = min(f[i][j], f[i - 1][j - 1]);//這裡就要求 i-1 和 b[j-1]相等的情況下 這裡i和j相等就是繼承之前的情況 else f[i][j] = min(f[i][j], f[i - 1][j - 1] + 1);// [i]和[j]不相等的情況下 需要在[i-1]和[j-1]相等的情況下 將i修改為和j相等 } printf("%d\n", f[n][m]); return 0; }
最長公共子序列https://www.acwing.com/problem/content/899/
#include <iostream> using namespace std; const int N = 1010; int n, m; char a[N], b[N]; int f[N][N];//表示i,j 對到達 a[i]和b[i]最長公共子序列的長度 f[i][j] int main() { cin >> n >> m >> a + 1 >> b + 1; for (int i = 1; i <= n; i++) { for (int j = 1; j <= m; j++) { if (a[i] == b[j]) { f[i][j] = f[i - 1][j - 1] + 1;//如果a[i]和b[i]字元相等 那麼就在他們前面情況基礎上a[i-1] b[j-1] +1 } else { f[i][j] = max(f[i - 1][j], f[i][j - 1]);//不相等的話 是不用+1 的只需要繼承之前的情況就行因為兩個字元一定有一個可以+1 表示遍歷到這個字元和之前情況一樣 } } } cout << f[n][m] << '\n'; return 0; }
區間合併 i j 表示區間【i,j】
先列舉區間len為1的
石子合併https://www.acwing.com/problem/content/284/
i和j 表示i個石頭到j個石頭合併的最小价值
合併但這能選擇相鄰兩個合併
#include <iostream>
#include <algorithm>
using namespace std;
const int N = 310;
int n;
int s[N];
int f[N][N];
int main()
{
scanf("%d", &n);
for (int i = 1; i <= n; i ++ ) scanf("%d", &s[i]);
for (int i = 1; i <= n; i ++ ) s[i] += s[i - 1];//用於快速求字首和
for (int len = 2; len <= n; len ++ )//
for (int i = 1; i + len - 1 <= n; i ++ )
{
int l = i, r = i + len - 1;//r是長度
f[l][r] = 1e8;//因為求最小值 先設定個較大值
for (int k = l; k < r; k ++ )//k是分割每個區間
f[l][r] = min(f[l][r], f[l][k] + f[k + 1][r] + s[r] - s[l - 1]);//l到k 和 k+1到r區間的最小值 +合併的消耗
}
printf("%d\n", f[1][n]);
return 0;
}
密碼脫落:最長迴文子序列
i表示原來字串的左端點 j表示原來字串的右邊端點
for 列舉長度
for 列舉左端點
f[i][j] i到j長度的 最長迴文字串長度
#include <iostream>
#include <cstring>
#include <algorithm>
using namespace std;
const int N = 1005;
int f[N][N];
char s[N];
int main()
{
scanf("%s", &s);
int n;n=strlen(s);
for (int len = 1; len <= n; len ++ ){//列舉長度
for (int i = 0; i+len-1 < n; i ++ ){//列舉左端點
int j=i+len-1;
if(len==1) f[i][j]=1;
else {
f[i][j]=max(f[i+1][j],f[i][j-1]);//左邊少一個的狀況 和 右邊少一個字母的狀況
if(s[i]==s[j]){
f[i][j]=max(f[i][j],f[i+1][j-1]+2);
}
}
}
}
cout << n-f[0][n-1];
return 0;
}