洛谷P1439 【模板】最长公共子序列 详细讲解

原题链接:点我

这道题其实是一个变相的最长上升子序列。欲哭无泪啊。

正传:

百度上搜了一下 LCS的求法,然后发现最优的 dp 就是 𝑂(𝑛2)?

但是注意到一个性质 ,两个数组都是 1~ 𝑛 的排列。

所以今天我们要秀出什么操作呢!

如何将原题从LCS(最长公共子序列)变为LIS(最长上升子序列)

        因为题中说明是每行为 𝑛 个数,为自然数 1,2,…,𝑛的一个排列。我们可以以第一个串为标准,用第二个串来匹配第一个串,看能匹配多少,所以,其实第一个串的每个数字其实影响不大,只有知道它对应了第二串的哪个数字就好了,那么我们为什么不把他给的串重新定义一下?

比如题中的样例:3 2 1 4 5 我们把它变成 1 2 3 4 5 用一个数组记录一下每个数字变成了什么,相当于离散化了一下3-1;2-2;1-3;4-4;5-5;

现在我们的第二串1 2 3 4 5 按我们离散化的表示:3 2 1 4 5。

如果你还没懂,在举一个栗子  

输入:
5
4 3 5 1 2
2 3 5 4 1

4 3 5 1 2 我们把它变成1 2 3 4 5  也就相当于离散化了一下,4-1 ;3-2;5-3;1-4;2-5;

第二串2 3 5 4 1直接就可以表示为5 2 3 1 4。(。◝ᴗ◜。)

懂了吧(没懂的在看几遍例子,别说话),我们把第一个串离散化后的数组是满足上升,反过来,满足上升的也就是满足原串的排列顺序的,O(∩_∩)O~

好了 ,现在的问题就变成了求一个最长不下降序列!代码如下。

for(int i = 1;i <= n;i++){
    cin >> a[i];
    num[a[i]] = i;//a[]数组中第i项的值离散化后应为i
}//num[]为离散化标记数组,num[某数]就是某数离散化后的结果
for(int i = 1;i <= n;i++){
    cin >> b[i];
    b[i] = num[b[i]]//a[]数组中第i项的值离散化后应为num[b[i]]
}

时间复杂度优化

看到最长上升子序列,我们一般会想到某种DP(至少我是这样,嘿嘿)。代码如下。

for(int i = 1;i <= n;i++){
    for(int j = i;j <= n;j++){
        if(a[j]>a[i]){
            dp[j] = max(dp[j],dp[i]+1);
        }
    }
}

我们其实不难看出,对于𝑛^2做法而言,其实就是暴力枚举:将每个状态都分别比较一遍。但其实有些没有必要的状态的枚举,导致浪费许多时间,当元素个数到了10^4以上时,就已经超时了。而此时,我们可以通过另一种动态规划的方式来降低时间复杂度:

将原来的dp数组的存储由数值换成该序列中,上升子序列长度为i的上升子序列,的最小末尾数值

这其实就是一种几近贪心的思想:我们当前的上升子序列长度如果已经确定,那么如果这种长度的子序列的结尾元素越小,后面的元素就可以更方便地加入到上升子序列中。

注:t为dp[]中第一个小于b[i]的数的下标

表达式

\left\{\begin{matrix} dp[++len] = b[i] & b[i]> dp[len] \\ dp[t] = b[i] & b[i]\leqslant dp[len] \end{matrix}\right.

就是每次都向前找比它小的数和比它大的数的位置,将第一个比它大的替换掉,这样操作虽然LIS序列的具体数字可能会变,但是很明显LIS长度还是不变的,因为只是把数替换掉了,并没有改变增加或者减少长度。但是我们通过这种方式是无法求出最长上升子序列具体是什么的,这点和最长公共子序列不同。

我们现在就转化为了t的值。为了使代码即简洁,复杂度又为O(nlogn)我们选用STL中的;lower_bound()函数(其实是不想打太多)。不会的自行搜查。ヾ(❀^ω^)ノ゙二分查找代码如下:

int len = 0;//len为dp[]数组的长度也是最终的结果
for(int i = 1;i <= n;i++){
    if(b[i] > dp[len]) dp[++len] = b[i];
    else {
        int t = lower_bound(dp+1,dp+len+1,b[i]) - dp;
        dp[t] = b[i];
    } 
}

本道题就完结了!!

完整代码

最后附上完整代码:

#include<bits/stdc++.h>
using namespace std;
int a[100001],b[100001],num[100001],dp[100001];
int main(){
    int n;
    cin >> n;
    for(int i = 1;i <= n;i++){
        cin >> a[i];
        num[a[i]] = i;
    } 
    for(int i = 1;i <= n;i++){
        cin >> b[i];
        b[i] = num[b[i]];
    }
    int len = 0;
    for(int i = 1;i <= n;i++){
        if(b[i] > dp[len]) dp[++len] = b[i];
        else {
            int t = lower_bound(dp+1,dp+len+1,b[i]) - dp;
            dp[t] = b[i];
        } 
    }
    cout << len << endl;
    return 0;
}

制作不易,感谢观看

麻烦dalao们用您金贵的手指点点关注呗

完结撒花
  • 14
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
P1807 最长路是一道洛谷的题目,要求计算一个有向无环图中1到n之间的最长路径。关于这个问题,可以使用广搜算法来依次搜索图中的结点,并记录当前结点的最长路并不断更新。具体的实现可以使用邻接矩阵来存储边权,并注意可能存在重边的情况需要取最大值。此外,为了避免超时,可以在入队时加上一个判断语句,只有当到达该点的最长路需要更新时才进行入队操作。以下是一种可能的解法: ```cpp #include <bits/stdc++.h> #define endl '\n' #define inf 0x3f3f3f3f using namespace std; typedef long long ll; const ll mo=80112002; int n, m, u, v, ww, w = max(w[u][v], ww); } q.push(1); memset(a, -1, sizeof(a)); a = 0; while (!q.empty()) { int x = q.front(); q.pop(); for (int i=1; i<=n; i++) { if (w[x][i && a[i < a[x + w[x][i]) { a[i = a[x + w[x][i]; q.push(i); } } } cout << a[n]; return 0; } ``` 以上是一种可能的代码实现,使用了广搜算法来计算最长路径。该算法使用邻接矩阵存储边权,通过不断更新当前结点的最长路来得到1到n之间的最长路径。<span class="em">1</span><span class="em">2</span><span class="em">3</span> #### 引用[.reference_title] - *1* [洛谷P1807 最长路(BFS)](https://blog.csdn.net/m0_60543948/article/details/126431087)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v93^chatsearchT3_2"}}] [.reference_item style="max-width: 50%"] - *2* *3* [图论——最长路(洛谷 P1807)](https://blog.csdn.net/weixin_44572229/article/details/120743825)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v93^chatsearchT3_2"}}] [.reference_item style="max-width: 50%"] [ .reference_list ]
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值