OVSolitario-io

导航

DP:关注题中哪些东西影响答案,造成了后续相同的局面,导致不关心前面的决策

DP:转移方程不唯一,不同看待问题方式造就不同结果

表格法:表格法

e.g.:维护最后一个信息

背包问题:背包

记忆化搜索:记忆化搜索

算法竞赛进阶指南:算法竞赛进阶指南合集


适用条件:截屏2025-10-17 10.25.22

造成了后续相同的局面,所以不关心前面的决策

线性DP:线性DP
区间DP:按区间分类,很多优化
区间DP

内容分类:
概率DP:求概率即可当作概率DP
概率DP
计数DP:计数DP

方法分类:
数位DP:重要的方式是不同情况的分类讨论
数位DP
状压DP:状压DP
树形DP:树形DP

优化:
DP优化:DP优化

状态之间视为点,状态的转移为点之间的有向边,则会构建一张DAG

无环:不会确定后面的值之后再去更新前面的值

根据上面思路:
无后效性: 当一个状态确定,则不会被后面的状态所影响

即算f[5]时,与f[5]前面的f[2],f[3]无关···即不关心前面(即使有多种算法)如何算出,只保证f[i]为最优

最优子结构: 子问题的最优解可以推导出整个问题的最优解

DP三要素:

  • 状态:描绘局面
  • 转移:状态间计算关系
  • 阶段:计算状态的顺序

对于DP思考路径,往往考虑状态的由来状态的去处

一维线性DP:状态定于与题设为线性相关DP

LIS为一条线,为一维线性DP,而如棋盘问题,为棋盘二维,为二维线性DP

再看LIS(n^2 => nlogn做法):维护LIS每个长度下最小结尾(单调) + 二分

对于朴素的LIS,会对其前面all元素都进行一次比较尝试更新,但其实对于长度为x + 1的值来说真正有用的只有前面长度为x的那几个值,发现对于相同长度的LIS,显然结尾的数越小越好(后面能接的数更多),那么在LIS长度相同时,我们保留最小的ai

动态的维护最小LIS的结尾

那么即得到下图:
截屏2025-08-31 18.01.42
此时当后面接7时,即将LIS5的9替换掉(更优),读2换3,读8则接到后面使LIS长度+1

LIS存在着单调性:当x<a'len(len为LIS长度)则替换,否则加入队尾
截屏2025-08-31 18.10.15
有着明显单调性,则这两个操作可以通过二分来实现

for(int i = 1; i <= n; ++ i) {
    //找第一个大于等于a[i]元素
    int pos = lower_bound(g + 1, g + len + 1, a[i]) - g;//在g中找到第一个大于等于a[i]的元素
    g[pos] = a[i];//将此为改为a[i]
    len = max(len, pos);//更新长度
}

**-g **是指针相减运算,用于计算偏移量,此处从1开始,得到的值会是第一个大于a[i]的值的位置
lower_bound返回第一个大于a[i]的值,减去指针偏移量变为第一个大于a[i]的值的位置

二维DP

数字三角形,二维通过(i, j)两个数来确定位置

LCS:
对于LCS的两个序列,无法用一维来定位,二维:定义为f[i][j]表示为数列A的前i位与数列B的前j位组成的LCS
此时有情况:

  • 匹配:长度+1
  • 不匹配:从前面长的过渡过来
    截屏2025-08-31 19.15.17

再看LCS:重标号构造单调递增
发现若数列A,B为同列(1~n都各出现一次),则有对序列重标号,此时局面为求一序列与另一单调递增序列求LCS,那么就是求序列中的最长LIS即可
截屏2025-08-31 19.26.52

其中32145为abcde,12345为cbade
那么因为单调递增,求另一个最长ade,一定可以再单调序列取到

即当1~n都出现一次,则可转换为求LIS问题,在套用前面nlogn求LIS
但存在强限制:AB都为数列,但很多时AB都为字符串

点击查看代码
#include <bits/stdc++.h>
using namespace std;
#define N 100010
int n;
int a1[N], b1[N], a[N], b[N], g[N], len;
int main() {
    cin >> n;
    for(int i = 1; i <= n; ++ i) {
        cin >> a1[i];
        a[a1[i]] = i;
    }
    for(int i = 1; i <= n; ++ i) {
        cin >> b1[i];
        b[i] = a[b1[i]];
        // cout << b[i] << ' ';
    }
    
    for(int i = 1; i <= n; ++ i) {
        //找第一个大于等于a[i]元素
        int pos = lower_bound(g + 1, g + len + 1, b[i]) - g;
        g[pos] = b[i];//将此为改为a[i]
        len = max(len, pos);//更新长度
    }
    
    cout << len << endl;
    return 0;
}

编辑距离:P2758 编辑距离
2串=>2维,f[i][j]为将a前i个字符,变为b的前j个字符的最小操作次数

三种操作:i拼出j最后一步一定选用了三种操作其一

  • 删除:f[i][j] = f[i - 1][j] + 1

选删除:多出这个字符,删除a[i] (不要这个字符),a的前i-1个字符变为j的最小操作

  • 插入:f[i][j] = f[i][j - 1] + 1

选插入:则当前无这个字符,所以前i个字符拼出前j - 1个字符即可,再加上b[j]这个字符

  • 修改:f[i][j] = f[i - 1][j - 1] + [Ai != Bi]

选修改:若a[i]!=b[j]进行那么修改操作

#include <bits/stdc++.h>
using namespace std;
int min(int x, int y, int z) {
    return min(min(x, y), z);
}
int edit_dist_dp(string str1, string str2) {
    int m = str1.length();
    int n = str2.length();
    vector<vector<int>> dp(m + 1, vector<int> (n + 1));
    for(int i = 0; i <= m; ++ i) {
        for(int j = 0; j <= n; ++ j) {
            //考虑边界
            if(i == 0) {
                dp[i][j] = j;//当A中字符数为0,要插入j次来构造j
            }
            else if(j == 0) {//当B中字符为0,A要删除j次来构造j
                dp[i][j] = i;
            }
            else if(str1[i - 1] == str2[j - 1]) {//A=B直接转移,不操作
                dp[i][j] = dp[i - 1][j - 1];
            }
            else {//否则计算最小操作步数
                dp[i][j] = 1 + min(dp[i][j - 1], dp[i - 1][j], dp[i - 1][j - 1]);
            }
        }
    }
    return dp[m][n];
}

int main() {
    string str1, str2;
    cin >> str1 >> str2;
    cout << "Mininum number of operation:" << edit_dist_dp(str1, str2) << endl;
    return 0;
}

posted on 2025-10-09 19:58  TBeauty  阅读(7)  评论(0)    收藏  举报