1. 程式人生 > 其它 >字串dp 核心[i,j] (1)表示 左邊端點為i 右邊端點為j(區間合併) 有時候for k切割區間 (2)表示i為第一個串第i個位置 j為第二個串第j個位置 (3).

字串dp 核心[i,j] (1)表示 左邊端點為i 右邊端點為j(區間合併) 有時候for k切割區間 (2)表示i為第一個串第i個位置 j為第二個串第j個位置 (3).

串問題

最短編輯距離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

j]匹配
那麼修改這一位之前,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;
}