就是感觉之前 dp 的 blog 太乱了整理一下。
LIS(最长上升子序列)
例题
给定一个整数序列,找到它的所有严格递增子序列中最长的序列,输出其长度。
思路
拿到题目,大家第一时间想到的应该是的暴力(dp)做法:
#include <bits/stdc++.h>
using namespace std;
int a[maxn],dp[maxn];
int main(){int n;cin>>n;for(int i=1;i<=n;i++){cin>>a[i];dp[i]=1;}for(int i=1;i<=n;i++){for(int j=1;j<i;j++){if(a[j]<a[i])dp[i]=max(dp[i],dp[j]+1);}}int ans=-1;for(int i=1;i<=n;i++) ans=max(ans,dp[i]);cout<<ans<<endl;return 0;
}
其中,表示以为结尾的最长上升子序列的长度。而这,也就是 dp 的做法。当然,我们可以用用贪心+二分优化这个算法。这里就不详细展开说了,但是给出代码:
#include <bits/stdc++.h>
using namespace std;
int mn[maxn],a[maxn];
int binary_search(int a[],int r,int x){//二分查找,返回a数组中第一个>=x的位置int left=1;while(left<=right){int mid=(left+right)>>1;if(a[mid]<=x)left=mid+1;else right=mid-1;}return left;
}
int main(){int n;cin>>n;for(int i=1;i<=n;i++) {cin>>a[i];mn[i]=INF;//由于mn中存的是最小值,所以mn初始化为INF }mn[1]=a[1]; int ans=1;//初始时LIS长度为1 for(int i=2;i<=n;i++){if(a[i]>mn[ans])//若a[i]>=mn[ans],直接把a[i]接到后面 mn[++ans]=a[i];else//否则,找到mn中第一个>=a[i]的位置mn[j],用a[i]更新mn[j] mn[binary_search(mn,ans,a[i])]=a[i];}cout<<ans<<endl; return 0;
}
时间复杂度:
LCS(最长公共子序列)
例题:P1439
思路
考虑暴力:设两个字符串的长度为n和m,对于两个字符串的每个字符,我们可以选,也可以不选。得到两个子序列后,比较是否相同要,所以时间复杂度 。
所以还是老老实实 dp 吧。
令表示字符串 s 的第 i 位和字符串 t 的第 j 位的LCS长度。
那么有两种情况:
- :那么字符串 s 和字符串 t 的LCS长度就增加 1。
- :那么字符串 s 和字符串 t 的LCS长度无法继续增加,等于。
Code
#include <bits/stdc++.h>
using namespace std;
int dp[maxn][maxm];//dp[i][j]表示s的第i位和t的第j位的LCS
int main(){string s,t;cin>>s>>t;for(int i=0;i<=s.size();i++) {dp[0][i]=0;dp[i][0]=0;}//初始化 for(int i=1;i<=s.size();i++){for(int j=1;j<=t.size();j++){if(s[i-1]==t[j-1])dp[i][j]=dp[i-1][j-1]+1;//相等就加1 elsedp[i][j]=max(dp[i-1][j],dp[i][j-1]);}//不相等就比较 }cout<<dp[s.size()][t.size()]<<endl;return 0;
}
LCIS(最长公共上升子序列)
例题:CF10D
思路
看到题目,第一反应就是先求 LCS,再求 LIS。
对于状态时 ,由于 dp 状态就决定了,是一定作为这个状态下 LCIS 的结尾的,所以对于,就有两种情况,将其纳入LCIS或者不纳入。
- ->
- ->
然后用滚动数组优化一下就OK力。
Code
#include <bits/stdc++.h>
using namespace std;
int dp[maxn],a[maxn],b[maxn];
int main(){int n;cin>>n;for(int i=1;i<=n;i++)cin>>a[i];for(int i=1;i<=n;i++)cin>>b[i];int ans=0;for(int i=1;i<=n;i++){int tmp=0;for(int j=1;j<=n;j++){if(a[i]==b[j])dp[j]=max(dp[j],tmp+1);if(a[i]>b[j])tmp=max(tmp,dp[j]); ans=max(dp[j],ans);}}cout<<ans<<endl;return 0;
}
字符串编辑距离
例题:P2758
思路
首先,如果 1 个字符串的长度为 0,那答案就是另一个字符串的长度。所以,我们令表示
转换为所需的最少操作次数。
也就是说:;。
接下来,继续思考,发现如果,那么相同,无需变化。
如果不相同,。
Code
#include <bits/stdc++.h>
using namespace std;
char a[maxn],b[maxn];
int dp[maxn][maxn];
int main(){int n;cin>>n;for(int i=1;i<=n;i++)cin>>a[i];for(int i=1;i<=n;i++)cin>>b[i];//极端情况for(int i=1;i<=strlen(a);i++)dp[i][0]=i;for(int j=1;j<=strlen(b);j++)dp[0][j]=j;for(int i=1;i<=strlen(a);i++){for(int j=1;j<=strlen(b);j++){if(a[i-1]==b[j-1])//相同时距离不变dp[i][j]=dp[i-1][j-1];else//不同时取三个位置的最小值再+1dp[i][j]=min(dp[i-1][j-1],min(dp[i-1][j],dp[i][j-1]))+1;}}cout<<dp[strlen(a)][strlen(b)]<<endl;return 0;
}
最大子序列和
例题
对于给定序列,寻找它的连续的最大和子数组。
思路
非常简单,你只需要比较一下和的大小就可以了。
Code
#include <bits/stdc++.h>
using namespace std;
int a[maxn],dp[maxn];
int main(){int n;cin>>n;for(int i=1;i<=n;i++)cin>>a[i];int ans=-inf;for(int i=2;i<=n;i++){dp[i]=max(dp[i-1]+a[i],a[i]);ans=max(ans,dp[i]);}cout<<ans<<endl;return 0;
}
数字三角形
例题
给出一个数字三角形,现在要从左上角走到第 i 行第 j 列,每一步只能走到相邻的结点,求经过的结点的最大数字和。
思路
非常简单,易得状态转移方程:。
Code
#include<bits/stdc++.h>
using namespace std;
int a[maxn][maxn],dp[maxn][maxn];
int main(){int n;cin>>n;for(int i=1;i<=n;i++){for(int j=1;j<=i;j++){cin>>a[i][j];dp[i][j]=a[i][j];}}for(int i=n-1;i>=1;i--){for(int j=1;j<=i;j++)dp[i][j]+=max(dp[i+1][j],dp[i+1][j+1]);}cout<<dp[1][1]<<endl;return 0;
}
最大子矩阵和
例题
给定一个n行m列的整数矩阵,现在要求一个子矩阵,使其各元素之和为最大。输出这个和。
思路
首先,我们知道这个矩阵最后肯定在第i行和第j行之间。所以,我们用一个数组记录这第 i 行到第 j 行的每一列的和(这句话可能有点绕,可以多读几遍) 。然后,第 i 行到第 j 行的最大子矩阵和就是这个数组的最大子段和。
Code
#include <bits/stdc++.h>
using namespace std;
int a[maxn][maxn];
int sum[maxn];//表示i-j行对应列元素的和
int main(){int n;cin>>n;for(int i=0;i<n;i++){for(int j=0;j<n;j++)cin>>a[i][j];}int ans=-INF;for(int i=0;i<n;i++){memset(sum,0,sizeof(sum));for(int j=i;j<n;j++){//下面是针对数组sum求最大子段和的动态规划算法int tmp=0;for(int k=0;k<n;k++){sum[k]+=a[j][k];tmp+=sum[k];if(tmp<0)tmp=sum[k];ans=max(tmp,ans);}}}cout<<ans<<endl;return 0;
}
结尾
这篇 blog 是 up 在 2024CSP-J 初赛前一天晚上赶出来的,主要还是因为 2023CSP-J 初赛考了太多线性 dp(虽然今年大概率不考了)。祝大家 2024CSP-J RP++!!!