文章目录
- 线性dp
- 数字三角形
- 题目
- 思路
- LIS(最长上升子序列)
- 代码(n^2)
- 二分优化(nlogn)
- LCS(最长公共子序列)
- 代码
- LCS——>>LIS
- 思路
- 代码
- 最长公共子串
- 最长公共上升子序列(LCIS)
线性dp
数字三角形
P1216 数字三角形 - 洛谷
题目
观察下面的数字金字塔。
写一个程序来查找从最高点到底部任意处结束的路径,使路径经过数字的和最大。每一步可以走到左下方的点也可以到达右下方的点。
思路
每个数字只能有上方,左上,数字递推而来。最后计算最底层最大值。
int a[1100][1100],f[1100][1100];
void solve()
{int n;cin>>n;fir(i,1,n)fir(j,1,i){cin>>a[i][j];}fir(i,1,n){fir(j,1,i)f[i][j]=max(f[i-1][j-1],f[i-1][j])+a[i][j];}int mx=0;fir(i,1,n)mx=max(f[n][i],mx);cout<<mx<<'\n';
}
-
那如果向左和向右移动次数相差不能大于1,最大值又是多少了?
易知,当n为偶数,最后一层落在的点一定在n/2或n/2+1.而n为奇数时,最后一层落在的点一定在n/2+1
LIS(最长上升子序列)
例题
B3637 最长上升子序列 - 洛谷 | 计算机科学教育新生态
数组f【i】,用来存储以a【i】作末尾的上升子序列长度。
代码(n^2)
#include<bits/stdc++.h>
using namespace std;
int a[1000050],f[100050];
int main()
{int n,ans=1;cin>>n;for(int i=1;i<=n;i++)cin>>a[i]; fill(f,f+n+2,1);//初始化for(int i=2;i<=n;i++){for(int j=1;j<i;j++){if(a[j]<a[i])//递增f[i]=max(f[i],f[j]+1);}ans=max(ans,f[i]);}cout<<ans<<'\n';
}
二分优化(nlogn)
维和一个数组b,存放上升子序列。 其中 b[i]表示长度为 i 的上升子序列的最小末尾元素。
从前往后遍历数组a
- a[i]>b[len]末尾元素,b[++len]=a[i]
- a[i]<=b[len],用a[i] 替换b数组第一个>=a[i]的元素
最终b数组的长度便是答案
用vector和lower_bound实现
#include<bits/stdc++.h>
using namespace std;
int n,a[1000050];
int main()
{cin>>n;vector<int> d;for(int i=0;i<n;i++){ cin>>a[i];auto it=lower_bound(d.begin(),d.end(),a[i]);if(it==d.end())d.push_back(a[i]);else *it=a[i];}cout<<d.size();
}
LCS(最长公共子序列)
这个视频讲的不错E05 线性DP 最长公共子序列,本文思路节选于此视频
例题
U165581 最长公共子序列(Longest Common Subsequence,LCS) - 洛谷 | 计算机科学教育新生态
设 f[i][j]
表示序列 a[1...i]
和 b[1...j]
的最长公共子序列长度。
现在判断末尾元素 a[i]
与 b[j]
是否在公共子序列中:
- 若
a[i] = b[j]
:- 则
a[i]
与b[j]
在公共子序列中。 - 递推关系:
f[i][j] = f[i-1][j-1] + 1
- 则
- 若
a[i] ≠ b[j]
,且a[i]
不在公共子序列中:- 则可去掉
a[i]
。 - 递推关系:
f[i][j] = f[i-1][j]
- 则可去掉
- 若
a[i] ≠ b[j]
,且b[j]
不在公共子序列中:- 则可去掉
b[j]
。 - 递推关系:
f[i][j] = f[i][j-1]
- 则可去掉
状态转移方程:
f [ i ] [ j ] = { f [ i − 1 ] [ j − 1 ] + 1 , 若 a [ i ] = b [ j ] max ( f [ i − 1 ] [ j ] , f [ i ] [ j − 1 ] ) , 若 a [ i ] ≠ b [ j ] f[i][j] = \begin{cases} f[i-1][j-1] + 1, & \text{若 } a[i] = b[j] \\ \max(f[i-1][j], f[i][j-1]), & \text{若 } a[i] \neq b[j] \end{cases} f[i][j]={f[i−1][j−1]+1,max(f[i−1][j],f[i][j−1]),若 a[i]=b[j]若 a[i]=b[j]
边界条件:
f [ 0 ] [ j ] = 0 ; f [ i ] [ 0 ] = 0 ; f[0][j]=0;f[i][0]=0; f[0][j]=0;f[i][0]=0;
代码
int n,f[N][N];
int main()
{ string a,b;cin>>a>>b;for(int i=0;i<a.size();i++)for(int j=0;j<b.size();j++){if(a[i]==b[j])f[i+1][j+1]=f[i][j]+1;elsef[i+1][j+1]=max(f[i][j+1],f[i+1][j]);}cout<<f[a.size()][b.size()];
}
LCS——>>LIS
例题
P1439 【模板】最长公共子序列 - 洛谷
思路
此题特殊之处在于:全排列
数据范围大,N^2不行
既然两个数组都是1-n的全排列,那么可以用一个新的数组c存放b[i]在数组a中的位置,只有c中递增的子序列才可构成a,b的公共子序列。这样题目就转化为求最长上升子序列了
代码
#include<bits/stdc++.h>
using namespace std;
const int N=1e5+10;
int n,a[N],b[N],c[N];
int main()
{ cin>>n;for(int i=1;i<=n;i++){cin>>a[i];b[a[i]]=i;//哈希}for(int i=1;i<=n;i++){ int x;cin>>x;c[i]=b[x];//LCS——>LIS}vector<int> v;//二分优化的最长上升子序列for(int i=1;i<=n;i++){auto it=lower_bound(v.begin(),v.end(),c[i]);if(it==v.end()) v.push_back(c[i]);else *it=c[i];}cout<<v.size();
}
最长公共子串
-
公共子串:字符必须是连续相等的
-
公共子序列:字符必须是相等的,可以不连续
设
f[i][j]
表示序列a[1...i]
和b[1...j]
的最长公共子串长度。
与最长公共子序列类似,递推公式有所不同。
f [ i ] [ j ] = { f [ i − 1 ] [ j − 1 ] + 1 , 若 a [ i ] = b [ j ] 0 , 若 a [ i ] ≠ b [ j ] f[i][j] = \begin{cases} f[i-1][j-1] + 1, & \text{若 } a[i] = b[j] \\\ 0, & \text{若 } a[i] \neq b[j]\end{cases} f[i][j]={f[i−1][j−1]+1, 0,若 a[i]=b[j]若 a[i]=b[j]
最长公共子串不一定是以n,m结尾的,需要比较出最大值。
最长公共上升子序列(LCIS)
这是LIS和LCS的结合。
动态规划状态转移
-
初始化:我们初始化一个二维数组 f,大小为 (n+1) x (n+1),并将所有元素初始化为 0。这里 n 是序列 A 和 B 的长度。
-
状态转移:
首先判断公共子序列中是否包含a[i]
-
不包含a [i]的子集,最大值是f[i-1] [j]
-
包含a[i] 的子集,将这个子集继续划分,依据是子序列的倒数第二个元素在b[]中是哪个数(也就是由谁递推而来)
所以需要一个mx,来记录在当前
i
的条件下,满足a[i] > b[k]
的f[i - 1][k] + 1
的前缀最大值。
-
-
优化:为了提高效率,我们可以在遍历 j 的过程中维护一个最大值 mx,表示在当前 i 的情况下,所有 f [i-1] [k] 的最大值,其中 k < j 且 B[k] < A[i]。这样,我们就可以在 O(1) 的时间更新 f[i][j]。
f[i][j]
表示所有a[1 ~ i]
和b[1 ~ j]
中以b[j]
结尾的公共上升子序列的长度最大值。mx
用于记录在当前i
的条件下,满足a[i] > b[k]
的f[i - 1][k] + 1
的前缀最大值。- 下面代码是一维优化后的代码
int a[3050],b[3050],f[3050];
void solve()
{int n,mx;cin>>n;fir(i,1,n)cin>>a[i];fir(i,1,n)cin>>b[i];fir(i,1,n)//只看前i个{mx=1;fir(j,1,n){if(a[i]==b[j]) f[j]=max(f[j],mx);if(a[i]>b[j]) mx=max(mx,f[j]+1);}}mx=0;fir(i,1,n)mx=max(f[i],mx);cout<<mx<<'\n';
}