参考课程是我高中信息竞赛邱老师的课程。
【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)的算法