ACM CSP竞赛笔记(十八)——动态规划-最长上升子序列 与 最长公共子序列

参考课程是我高中信息竞赛邱老师的课程。

【16-2-2 最长上升子序列】 https://www.bilibili.com/video/BV1qF4m1A7VD/?share_source=copy_web\&vd_source=2c56c6a2645587b49d62e5b12b253dca

【16-2-3 最长公共子序列】 https://www.bilibili.com/video/BV19t421w7Ks/?share_source=copy_web\&vd_source=2c56c6a2645587b49d62e5b12b253dca

【16-2-4 线性动态规划小结】 https://www.bilibili.com/video/BV1uz42167A8/?share_source=copy_web\&vd_source=2c56c6a2645587b49d62e5b12b253dca

【16-2-4 线性动态规划习题解答】 https://www.bilibili.com/video/BV1xH4y1G7C2/?share_source=copy_web\&vd_source=2c56c6a2645587b49d62e5b12b253dca

完整CSP ACM板子可以在我资料获取,我设置的0积分,如果要花钱来B站私我!

【DP】最长上升子序列 P3637

方法一:考虑转移方程

难点在于状态方程:

这里可以得到是 比当前节点小、且下标也小 的连续值+1

https://www.luogu.com.cn/problem/B3637

cpp 复制代码
#include<bits/stdc++.h>
using namespace std;
int n;
int F[1000005];
int a[1000005];

int main(){
    int max_len=0;
    cin>>n;
    for(int i=1;i<=n;i++){
        cin>>a[i];
    }
    F[0]=F[1]=1;
    for(int i=1;i<=n;i++){
        int max_pre=0;
        for(int j=1;j<=i;j++){
            if(a[j]<a[i]){
                max_pre=max(max_pre,F[j]);
            }
        }
        F[i]=max_pre+1;
        max_len=max(max_len,F[i]);
    }
    cout<<max_len<<endl;
}

方法二:考虑转换状态定义

cpp 复制代码
#include<bits/stdc++.h>
using namespace std;
//转移规则:判断其与F[curcnt]的大小,如果大,就加在后面 F[curcnt++]=cur;如果不可以,找出F数组中第一个大于等于cur的位置。二分。


int a[5005];
int F[5005];
int curcnt=0;

int main(){
    int n;
    cin>>n;
    F[0]=0;
    for(int i=1;i<=n;i++){
        cin>>a[i];
        if(a[i]>F[curcnt]) {
            F[++curcnt]=a[i];
        }else{
            int L=0,R=curcnt;
            while(R>L){
                int M=(L+R)/2;
                if(F[M]>=a[i]){
                    R=M;
                }else{
                    L=M+1;
                }
            }
            F[L]=a[i];
        }
    }
    cout<<curcnt<<endl;
}

【DP】最长公共子序列

任意两串 T446233

https://www.luogu.com.cn/problem/T446233

状态定义:Fij为X序列前i个和Y序列前j个元素中重合的数量

初始化:F0j=0 F0i=0;

目标状态:FNM

转移方程:如果XY下一个相同,那么变为Fij=Fi-1j-1+1;

否则 如果X+1与Y的公共子序列更长,则选他,如果X与Y+1的公共子序列更长,则选他。

cpp 复制代码
#include<bits/stdc++.h>
using namespace std;
int F[1005][1005];
int X[1005],Y[1005];
int main(){
    int N,M;
    cin>>N>>M;
    for(int i=1;i<=N;i++){
        cin>>X[i];
    }
    for(int i=1;i<=M;i++){
        cin>>Y[i];
    }
    for(int i=0;i<=N;i++){
        for(int j=0;j<=M;j++){
            if(i==0||j==0){
                F[i][j]=0;
            }else if(X[i]==Y[j]){//如果末尾相同
                F[i][j]=F[i-1][j-1]+1;
            }else{
                F[i][j]=max(F[i][j-1],F[i-1][j]);
            }
            
        }
    }
    cout<<F[N][M];
}

数量相同且不重复的两串 P1439

https://www.luogu.com.cn/problem/P1439

但是这样时间复杂度太高了。对于已知范围且相同数量的两个序列,可以用重映射的方式。

记f为Y的下标序列,将这组关系映射到X下,(5,1)(3,2)(4,3)(1,4)(2,5)=》2 5 4 3 1

就变成对1 2 3 4 5与2 5 4 3 1求公共子序列了。

也就是求25431的最长上升序列。

用Map映射是最好的方式

cpp 复制代码
#include<bits/stdc++.h>
using namespace std;
//用Map存映射
int X[100005],Y[100005];
map<int,int> m;
int F[100005];
int N;
int curcnt=0;
int main(){
    cin>>N;
    for(int i=1;i<=N;i++){
        cin>>X[i];
        m[X[i]]=i;//绑定{X[1],1}{X[2],2}
    }
    for(int j=1;j<=N;j++){
        int t;
        cin>>t;
        auto it=m.find(t);
        Y[j]=it->second;
    }
    //Y现在是一个最长升序问题
    //转移规则:判断其与F[curcnt]的大小,如果大,就加在后面 F[curcnt++]=cur;如果不可以,找出F数组中第一个大于等于cur的位置。二分。
    F[0]=0;
    for(int i=1;i<=N;i++){
        if(Y[i]>F[curcnt]){
            F[++curcnt]=Y[i];
        }else{
            int L=1,R=curcnt;//在之前的下标找
            while(R>L){
                int M=(R+L)/2;
                if(F[M]>=Y[i]){
                    R=M;
                }else{
                    L=M+1;
                }
            }
            F[L]=Y[i];
        }
        
    }
    cout<<curcnt;
}

线性DP练习小结

最优解问题------DP

最优解问题+单调------二分

先假设是DP问题,然后找是否满足:

注意DP的复杂度!

如果数据给到10^6,就是需要O(NlogN)的算法