最长公共子序列(洛谷P2516)
最长公共子序列(洛谷P2516)
题目大意
给定两个以.结尾的只含大写英文字母的字符串,求其最长公共子序列长度,以及构成最长公共子序列的方案数 。 ()
题解
由最长公共子序列容易想到用dp动态规划进行求解,设为匹配了第一个字符串的前个,第二个字符串的前个的最长公共子序列。注:以下涉及第个特指第一个字符串的第个(即),同理第个(即)。
首先我不匹配第个字符,转移方程为:
不匹配第个字符:
匹配第个字符和第个字符,同时字符相等的情况下:
这样我们就得到了状态转移方程:
第一个问题解决了,然后就是方案数的问题了,方案数其实和长度的转移方程差不多,表示构成最大长度的方案数,因为由三个地方转移而来,那么也应由这些地方转移而来,需要注意的是,上述状态转移方程中会被转移到和中,这在求长度中虽然不会影响答案,但是再求方案数时,我们会发现多计算了一次,那么在去重时,我们会发现,当且仅当等于时,重复计算的情况才会发生。
当等于时,显然没有发生的转移,但是其方案数和长度会转移到和中,所以会多计算一次的贡献。
当不等于时,可以发现的贡献永远不会转移到中,也就不需要去重
然后就可以写代码了
#include<bits/stdc++.h>
using namespace std;
typedef long long ll ;
const int N = 5e5 + 5;
const ll mod = 1e8;
const ll inf = 1e9;
int dp[5050][5050];
int cnt[5050][5050];
char s1[5050], s2[5050];
int main() {
scanf("%s%s", s1 + 1, s2 + 1);
int n = strlen(s1 + 1) - 1, m = strlen(s2 + 1) - 1;
for(int i = 0; i <= m; ++i)cnt[0][i] = 1;
for(int i = 0; i <= n; ++i)cnt[i][0] = 1;
for(int i = 1; i <= n; ++i){
for(int j = 1; j <= m; ++j){
if(s1[i] == s2[j]){
dp[i][j] = dp[i - 1][j - 1] + 1;
cnt[i][j] = cnt[i - 1][j - 1];
}
if(dp[i][j] == dp[i - 1][j]){
cnt[i][j] = (cnt[i][j] + cnt[i - 1][j]) % mod;
}else if(dp[i][j] < dp[i - 1][j]){
dp[i][j] = dp[i - 1][j];
cnt[i][j] = cnt[i - 1][j];
}
if(dp[i][j] == dp[i][j - 1]){
cnt[i][j] = (cnt[i][j] + cnt[i][j - 1]) % mod;
}else if(dp[i][j] < dp[i][j - 1]){
dp[i][j] = dp[i][j - 1];
cnt[i][j] = cnt[i][j - 1];
}
if(dp[i - 1][j - 1] == dp[i][j]){
cnt[i][j] = (cnt[i][j] - cnt[i - 1][j - 1] + mod) % mod;
}
}
}
cout << dp[n][m] << '\n' << cnt[n][m];
return 0;
}
然而这样空间复杂度是的,会MLE,我们会发现这个转移第位的转移只与第位有关,可以开一维空间数组记的状态,利用滚动数组优化第一维空间,空间复杂度就会变成的了
#include<bits/stdc++.h>
using namespace std;
typedef long long ll ;
const int N = 5e5 + 5;
const ll mod = 1e8;
const ll inf = 1e9;
void read();
int dp[5050], cnt[5050];
int ldp[5050], lcnt[5050];//上一位的状态
char s1[5050], s2[5050];
int main() {
scanf("%s%s", s1 + 1, s2 + 1);
int n = strlen(s1 + 1) - 1, m = strlen(s2 + 1) - 1;
for(int i = 0; i <= m; ++i)lcnt[i] = 1;
cnt[0] = 1;
for(int i = 1; i <= n; ++i){
for(int j = 1; j <= m; ++j){
if(s1[i] == s2[j]){
dp[j] = ldp[j - 1] + 1;
cnt[j] = lcnt[j - 1];
}
if(dp[j] == ldp[j]){
cnt[j] = (cnt[j] + lcnt[j]) % mod;
}else if(dp[j] < ldp[j]){
dp[j] = ldp[j];
cnt[j] = lcnt[j];
}
if(dp[j] == dp[j - 1]){
cnt[j] = (cnt[j] + cnt[j - 1]) % mod;
}else if(dp[j] < dp[j - 1]){
dp[j] = dp[j - 1];
cnt[j] = cnt[j - 1];
}
if(ldp[j - 1] == dp[j]){ //去重
cnt[j] = (cnt[j] - lcnt[j - 1] + mod) % mod;
}
}
for(int j = 1; j <= m; ++j){
ldp[j] = dp[j];
lcnt[j] = cnt[j];
dp[j] = cnt[j] = 0;
}
}
cout << ldp[m] << '\n' << lcnt[m];
return 0;
}
void read(){
#ifdef YYT
freopen("in.txt", "r", stdin);
freopen("out.txt", "w", stdout);
#endif
}
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 分享一个免费、快速、无限量使用的满血 DeepSeek R1 模型,支持深度思考和联网搜索!
· 使用C#创建一个MCP客户端
· 基于 Docker 搭建 FRP 内网穿透开源项目(很简单哒)
· ollama系列1:轻松3步本地部署deepseek,普通电脑可用
· 按钮权限的设计及实现