LCS 问题解释

article/2025/6/22 1:33:02

最长公共子序列问题(Longest Common Subsequence),该问题可以表述为,在 A , B A,B A,B 中找出一段子序列 x x x,使得 x x x 既是 A A A 的子序列,又是 B B B 的子序列。

你可以理解为,在两个序列中分别删除一些元素(剩下的不一定连续),使得两个序列的剩余部分相同且长度最长。

暴力解法可以用 DFS,但是时间复杂度为 O ( ∣ A ∣ × 2 ∣ B ∣ ) O(|A|\times 2^{|B|}) O(A×2B),不能够满足我们的需求。

考虑动态规划。

定义:设 d p i , j dp_{i,j} dpi,j 表示 A A A 序列前 i i i 个与 B B B 序列的前 j j j 个的最长公共子序列。

答案为 d p n , m dp_{n,m} dpn,m

状态转移:

d p i , j = { d p i − 1 , j − 1 A i = B j max ⁡ ( d p i − 1 , j , d p i , j − 1 ) otherwise dp_{i,j}=\begin{cases} dp_{i-1,j-1}& A_i=B_j\\ \max(dp_{i-1,j},dp_{i,j-1})&\text{otherwise} \end{cases} dpi,j={dpi1,j1max(dpi1,j,dpi,j1)Ai=Bjotherwise

但如果需要输出任意合法路径(题目:51Nod - 1006),怎么办?

递归倒推回去。

f ( x , y ) f(x,y) f(x,y) 表示 A A A 序列前 x x x 个和 B B B 序列前 y y y 个的匹配情况。

如果 A x = B y A_x=B_y Ax=By,那么说明选择了这个点,输出 A x A_x Ax(或 B y B_y By),同时调用 f ( x − 1 , y − 1 ) f(x-1,y-1) f(x1,y1)

否则,如果 d p x − 1 , y > d p x , y − 1 dp_{x-1,y}>dp_{x,y-1} dpx1,y>dpx,y1,调用 f ( x − 1 , y ) f(x-1,y) f(x1,y)

否则调用 f ( x , y − 1 ) f(x,y-1) f(x,y1)

实现

#include<bits/stdc++.h>
using namespace std;
#define int long long
int dp[1005][1005];
string s,t;
void print(int x,int y){if(!x||!y){return;}if(s[x]==t[y]){print(x-1,y-1);cout<<s[x];}else{if(dp[x-1][y]>dp[x][y-1]){print(x-1,y);}else{print(x,y-1);}}
}
signed main(){ios::sync_with_stdio(0),cin.tie(0),cout.tie(0);cin>>s>>t;s=' '+s,t=' '+t;for(int i=1;i<s.size();i++){for(int j=1;j<t.size();j++){if(s[i]==t[j]){dp[i][j]=dp[i-1][j-1]+1;}else{dp[i][j]=max(dp[i-1][j],dp[i][j-1]);}}}print(s.size()-1,t.size()-1);return 0;
}

HDU-1159 Common Subsequence

最基础的那道题,本来应该放前面的。

d p i , j dp_{i,j} dpi,j 表示 A A A 序列前 i i i 个与 B B B 序列的前 j j j 个的最长公共子序列。

答案为 d p n , m dp_{n,m} dpn,m

状态转移:

d p i , j = { d p i − 1 , j − 1 A i = B j max ⁡ ( d p i − 1 , j , d p i , j − 1 ) otherwise dp_{i,j}=\begin{cases} dp_{i-1,j-1}& A_i=B_j\\ \max(dp_{i-1,j},dp_{i,j-1})&\text{otherwise} \end{cases} dpi,j={dpi1,j1max(dpi1,j,dpi,j1)Ai=Bjotherwise

注意:*.size() 类型为无符号整型,不管在哪个容器都一样。

实现

#include<bits/stdc++.h>
using namespace std;
#define int long long
int dp[1005][1005]; // 设 dp[i][j] 表示 A 串前 i 个字符和 B 串前 j 个字符的最长公共子序列
string s,t;
signed main(){ios::sync_with_stdio(0),cin.tie(0),cout.tie(0);while(cin>>s>>t){s=' '+s,t=' '+t;for(int i=1;i<s.size();i++){for(int j=1;j<t.size();j++){if(s[i]==t[j]){dp[i][j]=dp[i-1][j-1]+1;}else{dp[i][j]=max(dp[i-1][j],dp[i][j-1]);}}}cout<<dp[s.size()-1][t.size()-1]<<'\n';}return 0;
}

U118717 最长公共上升子序列

注意:该做法不太严谨,数据太水,本来应该会 TLE 的。

d p i , j dp_{i,j} dpi,j 表示 A A A 序列前 i i i 个与 B B B 序列的前 j j j 个的最长公共上升子序列。

答案: max ⁡ { d p i , j } \max\{dp_{i,j}\} max{dpi,j}

计算前先进行 d p i , j = d p i − 1 , j dp_{i,j}=dp_{i-1,j} dpi,j=dpi1,j

A i = B j A_i=B_j Ai=Bj,那么 d p i , j = max ⁡ ( d p [ i ] [ j ] , 1 ) dp_{i,j}=\max(dp[i][j],1) dpi,j=max(dp[i][j],1)

枚举 k ( k ∈ [ 1 , j − 1 ] ) k(k\in[1,j-1]) k(k[1,j1]),若 b k < b j b_k<b_j bk<bj,则:

d p i , j = max ⁡ ( d p i , j , d p i , k + 1 ) dp_{i,j}=\max(dp_{i,j},dp_{i,k}+1) dpi,j=max(dpi,j,dpi,k+1)

实现

#include<bits/stdc++.h>
using namespace std;
#define int long long
int n,dp[3005][3005],a[3005],b[3005],ans;
signed main(){ios::sync_with_stdio(0),cin.tie(0),cout.tie(0);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<=n;i++){for(int j=1;j<=n;j++){dp[i][j]=dp[i-1][j];if(a[i]==b[j]){dp[i][j]=max(dp[i-1][j],1ll);for(int k=1;k<j;k++){if(b[k]<b[j]){dp[i][j]=max(dp[i][j],dp[i][k]+1);}}}ans=max(ans,dp[i][j]);}}cout<<ans;return 0;
}

P2516 [HAOI2010] 最长公共子序列

d p i , j dp_{i,j} dpi,j 表示 A A A 序列前 i i i 个与 B B B 序列的前 j j j 个的最长公共上升子序列, p a t h i , j path_{i,j} pathi,j 表示 A A A 序列前 i i i 个与 B B B 序列的前 j j j 个的最长公共上升子序列的方案数。

先处理出 d p i , j dp_{i,j} dpi,j。然后考虑如何处理 p a t h i , j path_{i,j} pathi,j

d p i , j = d p i − 1 , j dp_{i,j}=dp_{i-1,j} dpi,j=dpi1,j,说明是从 d p i − 1 , j dp_{i-1,j} dpi1,j 转移过来的,那么:

p a t h i , j = ( p a t h i , j + p a t h i − 1 , j ) m o d 10 8 path_{i,j}=(path_{i,j}+path_{i-1,j})\bmod 10^8 pathi,j=(pathi,j+pathi1,j)mod108

d p i , j = d p i , j − 1 dp_{i,j}=dp_{i,j-1} dpi,j=dpi,j1,则:

p a t h i , j = ( p a t h i , j + p a t h i , j − 1 ) m o d 10 8 path_{i,j}=(path_{i,j}+path_{i,j-1})\bmod 10^8 pathi,j=(pathi,j+pathi,j1)mod108

A i = B j A_i=B_j Ai=Bj d p i , j = d p i − 1 , j − 1 + 1 dp_{i,j}=dp_{i-1,j-1}+1 dpi,j=dpi1,j1+1,则:

p a t h i , j = ( p a t h i , j + p a t h i − 1 , j − 1 ) m o d 10 8 path_{i,j}=(path_{i,j}+path_{i-1,j-1})\bmod 10^8 pathi,j=(pathi,j+pathi1,j1)mod108

d p i − 1 , j − 1 = d p i , j dp_{i-1,j-1}=dp_{i,j} dpi1,j1=dpi,j,那么说明 d p i − 1 , j − 1 dp_{i-1,j-1} dpi1,j1 会重复计算,则:

p a t h i , j = ( p a t h i , j − p a t h i − 1 , j − 1 ) m o d 10 8 path_{i,j}=(path_{i,j}-path_{i-1,j-1})\bmod 10^8 pathi,j=(pathi,jpathi1,j1)mod108

实现

#include<bits/stdc++.h>
using namespace std;
const int mod=1e8;
string s,t;
int dp[5005][5005],n,m,path[5005][5005];
signed main(){ios::sync_with_stdio(0),cin.tie(0),cout.tie(0);cin>>s>>t;n=s.size(),m=t.size();s=' '+s,t=' '+t;n--,m--;for(int i=1;i<=n;i++){for(int j=1;j<=m;j++){if(s[i]==t[j]){dp[i][j]=dp[i-1][j-1]+1;}else{dp[i][j]=max(dp[i-1][j],dp[i][j-1]);}}}for(int i=0;i<=m;i++){path[0][i]=1;}for(int i=0;i<=n;i++){path[i][0]=1;}for(int i=1;i<=n;i++){for(int j=1;j<=m;j++){if(dp[i][j]==dp[i-1][j]){path[i][j]=(path[i][j]+path[i-1][j])%mod;}if(dp[i][j]==dp[i][j-1]){path[i][j]=(path[i][j]+path[i][j-1])%mod;}if(s[i]==t[j]&&dp[i][j]==dp[i-1][j-1]+1){path[i][j]=(path[i][j]+path[i-1][j-1])%mod;}if(dp[i-1][j-1]==dp[i][j]){path[i][j]=((path[i][j]-path[i-1][j-1])%mod+mod)%mod;}}}cout<<dp[n][m]<<'\n'<<path[n][m];return 0;
}

提交上去你就会发现是 70 70 70 分。

只有可怜的 125MB 空间显然过不了。

考虑滚动优化。

可以发现 p a t h path path 只涉及到 i − 1 i-1 i1 i i i 层,可以开两个数字分别处理第 i i i 层和第 i − 1 i-1 i1 层。

i − 1 i-1 i1 层用 p r e pre pre,进行存储。

然后:

d p i , j = d p i − 1 , j dp_{i,j}=dp_{i-1,j} dpi,j=dpi1,j,说明是从 d p i − 1 , j dp_{i-1,j} dpi1,j 转移过来的,那么:

p a t h j = ( p a t h j + p r e j ) m o d 10 8 path_j=(path_j+pre_j)\bmod 10^8 pathj=(pathj+prej)mod108

d p i , j = d p i , j − 1 dp_{i,j}=dp_{i,j-1} dpi,j=dpi,j1,则:

p a t h j = ( p a t h j + p a t h j − 1 ) m o d 10 8 path_j=(path_j+path_{j-1})\bmod 10^8 pathj=(pathj+pathj1)mod108

A i = B j A_i=B_j Ai=Bj d p i , j = d p i − 1 , j − 1 + 1 dp_{i,j}=dp_{i-1,j-1}+1 dpi,j=dpi1,j1+1,则:

p a t h j = ( p a t h j + p r e j − 1 ) m o d 10 8 path_j=(path_j+pre_{j-1})\bmod 10^8 pathj=(pathj+prej1)mod108

d p i − 1 , j − 1 = d p i , j dp_{i-1,j-1}=dp_{i,j} dpi1,j1=dpi,j,那么说明 d p i − 1 , j − 1 dp_{i-1,j-1} dpi1,j1 会重复计算,则:

p a t h i , j = ( p a t h i , j − p r e j − 1 ) m o d 10 8 path_{i,j}=(path_{i,j}-pre_{j-1})\bmod 10^8 pathi,j=(pathi,jprej1)mod108

初始化:

i ∈ [ 0 , m ] , p r e i = 1 i ∈ [ 0 , n ] , p a t h i = 1 i\in[0,m],pre_i=1\\ i\in[0,n],path_i=1 i[0,m],prei=1i[0,n],pathi=1

注意: p a t h path path 数组每一层都需要清空。

实现

#include<bits/stdc++.h>
using namespace std;
const int mod=1e8;
string s,t;
int dp[5005][5005],n,m,path[5005],pre[5005];
signed main(){ios::sync_with_stdio(0),cin.tie(0),cout.tie(0);cin>>s>>t;n=s.size(),m=t.size();s=' '+s,t=' '+t;n--,m--;for(int i=1;i<=n;i++){for(int j=1;j<=m;j++){if(s[i]==t[j]){dp[i][j]=dp[i-1][j-1]+1;}else{dp[i][j]=max(dp[i-1][j],dp[i][j-1]);}}}for(int i=0;i<=m;i++){pre[i]=1;}for(int i=0;i<=n;i++){path[i]=1;}for(int i=1;i<=n;i++){for(int j=1;j<=m;j++){path[j]=0;if(dp[i][j]==dp[i-1][j]){path[j]=(path[j]+pre[j])%mod;}if(dp[i][j]==dp[i][j-1]){path[j]=(path[j]+path[j-1])%mod;}if(s[i]==t[j]&&dp[i][j]==dp[i-1][j-1]+1){path[j]=(path[j]+pre[j-1])%mod;}if(dp[i-1][j-1]==dp[i][j]){path[j]=((path[j]-pre[j-1])%mod+mod)%mod;}}for(int j=1;j<=m;j++){pre[j]=path[j];}}cout<<dp[n][m]<<'\n'<<path[m];return 0;
}
```最长公共子序列问题(Longest Common Subsequence),该问题可以表述为,在 $A,B$ 中找出一段子序列 $x$,使得 $x$ 既是 $A$ 的子序列,又是 $B$ 的子序列。![](https://cdn.luogu.com.cn/upload/image_hosting/8c6poexo.png)你可以理解为,在两个序列中分别删除一些元素(剩下的不一定连续),使得两个序列的剩余部分相同且长度最长。暴力解法可以用 DFS,但是时间复杂度为 $O(|A|\times 2^{|B|})$,不能够满足我们的需求。考虑动态规划。定义:设 $dp_{i,j}$ 表示 $A$ 序列前 $i$ 个与 $B$ 序列的前 $j$ 个的最长公共子序列。答案为 $dp_{n,m}$。状态转移:$$
dp_{i,j}=\begin{cases}
dp_{i-1,j-1}& A_i=B_j\\
\max(dp_{i-1,j},dp_{i,j-1})&\text{otherwise}
\end{cases}
$$但如果需要输出任意合法路径(题目:[51Nod - 1006](https://vjudge.net/problem/51Nod-1006)),怎么办?递归倒推回去。设 $f(x,y)$ 表示 $A$ 序列前 $x$ 个和 $B$ 序列前 $y$ 个的匹配情况。如果 $A_x=B_y$,那么说明选择了这个点,输出 $A_x$(或 $B_y$),同时调用 $f(x-1,y-1)$。否则,如果 $dp_{x-1,y}>dp_{x,y-1}$,调用 $f(x-1,y)$。否则调用 $f(x,y-1)$。
### 实现
```cpp
#include<bits/stdc++.h>
using namespace std;
#define int long long
int dp[1005][1005];
string s,t;
void print(int x,int y){if(!x||!y){return;}if(s[x]==t[y]){print(x-1,y-1);cout<<s[x];}else{if(dp[x-1][y]>dp[x][y-1]){print(x-1,y);}else{print(x,y-1);}}
}
signed main(){ios::sync_with_stdio(0),cin.tie(0),cout.tie(0);cin>>s>>t;s=' '+s,t=' '+t;for(int i=1;i<s.size();i++){for(int j=1;j<t.size();j++){if(s[i]==t[j]){dp[i][j]=dp[i-1][j-1]+1;}else{dp[i][j]=max(dp[i-1][j],dp[i][j-1]);}}}print(s.size()-1,t.size()-1);return 0;
}

HDU-1159 Common Subsequence

最基础的那道题,本来应该放前面的。

d p i , j dp_{i,j} dpi,j 表示 A A A 序列前 i i i 个与 B B B 序列的前 j j j 个的最长公共子序列。

答案为 d p n , m dp_{n,m} dpn,m

状态转移:

d p i , j = { d p i − 1 , j − 1 A i = B j max ⁡ ( d p i − 1 , j , d p i , j − 1 ) otherwise dp_{i,j}=\begin{cases} dp_{i-1,j-1}& A_i=B_j\\ \max(dp_{i-1,j},dp_{i,j-1})&\text{otherwise} \end{cases} dpi,j={dpi1,j1max(dpi1,j,dpi,j1)Ai=Bjotherwise

注意:*.size() 类型为无符号整型,不管在哪个容器都一样。

实现

#include<bits/stdc++.h>
using namespace std;
#define int long long
int dp[1005][1005]; // 设 dp[i][j] 表示 A 串前 i 个字符和 B 串前 j 个字符的最长公共子序列
string s,t;
signed main(){ios::sync_with_stdio(0),cin.tie(0),cout.tie(0);while(cin>>s>>t){s=' '+s,t=' '+t;for(int i=1;i<s.size();i++){for(int j=1;j<t.size();j++){if(s[i]==t[j]){dp[i][j]=dp[i-1][j-1]+1;}else{dp[i][j]=max(dp[i-1][j],dp[i][j-1]);}}}cout<<dp[s.size()-1][t.size()-1]<<'\n';}return 0;
}

U118717 最长公共上升子序列

注意:该做法不太严谨,数据太水,本来应该会 TLE 的。

d p i , j dp_{i,j} dpi,j 表示 A A A 序列前 i i i 个与 B B B 序列的前 j j j 个的最长公共上升子序列。

答案: max ⁡ { d p i , j } \max\{dp_{i,j}\} max{dpi,j}

计算前先进行 d p i , j = d p i − 1 , j dp_{i,j}=dp_{i-1,j} dpi,j=dpi1,j

A i = B j A_i=B_j Ai=Bj,那么 d p i , j = max ⁡ ( d p [ i ] [ j ] , 1 ) dp_{i,j}=\max(dp[i][j],1) dpi,j=max(dp[i][j],1)

枚举 k ( k ∈ [ 1 , j − 1 ] ) k(k\in[1,j-1]) k(k[1,j1]),若 b k < b j b_k<b_j bk<bj,则:

d p i , j = max ⁡ ( d p i , j , d p i , k + 1 ) dp_{i,j}=\max(dp_{i,j},dp_{i,k}+1) dpi,j=max(dpi,j,dpi,k+1)

实现

#include<bits/stdc++.h>
using namespace std;
#define int long long
int n,dp[3005][3005],a[3005],b[3005],ans;
signed main(){ios::sync_with_stdio(0),cin.tie(0),cout.tie(0);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<=n;i++){for(int j=1;j<=n;j++){dp[i][j]=dp[i-1][j];if(a[i]==b[j]){dp[i][j]=max(dp[i-1][j],1ll);for(int k=1;k<j;k++){if(b[k]<b[j]){dp[i][j]=max(dp[i][j],dp[i][k]+1);}}}ans=max(ans,dp[i][j]);}}cout<<ans;return 0;
}

P2516 [HAOI2010] 最长公共子序列

d p i , j dp_{i,j} dpi,j 表示 A A A 序列前 i i i 个与 B B B 序列的前 j j j 个的最长公共上升子序列, p a t h i , j path_{i,j} pathi,j 表示 A A A 序列前 i i i 个与 B B B 序列的前 j j j 个的最长公共上升子序列的方案数。

先处理出 d p i , j dp_{i,j} dpi,j。然后考虑如何处理 p a t h i , j path_{i,j} pathi,j

d p i , j = d p i − 1 , j dp_{i,j}=dp_{i-1,j} dpi,j=dpi1,j,说明是从 d p i − 1 , j dp_{i-1,j} dpi1,j 转移过来的,那么:

p a t h i , j = ( p a t h i , j + p a t h i − 1 , j ) m o d 10 8 path_{i,j}=(path_{i,j}+path_{i-1,j})\bmod 10^8 pathi,j=(pathi,j+pathi1,j)mod108

d p i , j = d p i , j − 1 dp_{i,j}=dp_{i,j-1} dpi,j=dpi,j1,则:

p a t h i , j = ( p a t h i , j + p a t h i , j − 1 ) m o d 10 8 path_{i,j}=(path_{i,j}+path_{i,j-1})\bmod 10^8 pathi,j=(pathi,j+pathi,j1)mod108

A i = B j A_i=B_j Ai=Bj d p i , j = d p i − 1 , j − 1 + 1 dp_{i,j}=dp_{i-1,j-1}+1 dpi,j=dpi1,j1+1,则:

p a t h i , j = ( p a t h i , j + p a t h i − 1 , j − 1 ) m o d 10 8 path_{i,j}=(path_{i,j}+path_{i-1,j-1})\bmod 10^8 pathi,j=(pathi,j+pathi1,j1)mod108

d p i − 1 , j − 1 = d p i , j dp_{i-1,j-1}=dp_{i,j} dpi1,j1=dpi,j,那么说明 d p i − 1 , j − 1 dp_{i-1,j-1} dpi1,j1 会重复计算,则:

p a t h i , j = ( p a t h i , j − p a t h i − 1 , j − 1 ) m o d 10 8 path_{i,j}=(path_{i,j}-path_{i-1,j-1})\bmod 10^8 pathi,j=(pathi,jpathi1,j1)mod108

实现

#include<bits/stdc++.h>
using namespace std;
const int mod=1e8;
string s,t;
int dp[5005][5005],n,m,path[5005][5005];
signed main(){ios::sync_with_stdio(0),cin.tie(0),cout.tie(0);cin>>s>>t;n=s.size(),m=t.size();s=' '+s,t=' '+t;n--,m--;for(int i=1;i<=n;i++){for(int j=1;j<=m;j++){if(s[i]==t[j]){dp[i][j]=dp[i-1][j-1]+1;}else{dp[i][j]=max(dp[i-1][j],dp[i][j-1]);}}}for(int i=0;i<=m;i++){path[0][i]=1;}for(int i=0;i<=n;i++){path[i][0]=1;}for(int i=1;i<=n;i++){for(int j=1;j<=m;j++){if(dp[i][j]==dp[i-1][j]){path[i][j]=(path[i][j]+path[i-1][j])%mod;}if(dp[i][j]==dp[i][j-1]){path[i][j]=(path[i][j]+path[i][j-1])%mod;}if(s[i]==t[j]&&dp[i][j]==dp[i-1][j-1]+1){path[i][j]=(path[i][j]+path[i-1][j-1])%mod;}if(dp[i-1][j-1]==dp[i][j]){path[i][j]=((path[i][j]-path[i-1][j-1])%mod+mod)%mod;}}}cout<<dp[n][m]<<'\n'<<path[n][m];return 0;
}

提交上去你就会发现是 70 70 70 分。

只有可怜的 125MB 空间显然过不了。

考虑滚动优化。

可以发现 p a t h path path 只涉及到 i − 1 i-1 i1 i i i 层,可以开两个数字分别处理第 i i i 层和第 i − 1 i-1 i1 层。

i − 1 i-1 i1 层用 p r e pre pre,进行存储。

然后:

d p i , j = d p i − 1 , j dp_{i,j}=dp_{i-1,j} dpi,j=dpi1,j,说明是从 d p i − 1 , j dp_{i-1,j} dpi1,j 转移过来的,那么:

p a t h j = ( p a t h j + p r e j ) m o d 10 8 path_j=(path_j+pre_j)\bmod 10^8 pathj=(pathj+prej)mod108

d p i , j = d p i , j − 1 dp_{i,j}=dp_{i,j-1} dpi,j=dpi,j1,则:

p a t h j = ( p a t h j + p a t h j − 1 ) m o d 10 8 path_j=(path_j+path_{j-1})\bmod 10^8 pathj=(pathj+pathj1)mod108

A i = B j A_i=B_j Ai=Bj d p i , j = d p i − 1 , j − 1 + 1 dp_{i,j}=dp_{i-1,j-1}+1 dpi,j=dpi1,j1+1,则:

p a t h j = ( p a t h j + p r e j − 1 ) m o d 10 8 path_j=(path_j+pre_{j-1})\bmod 10^8 pathj=(pathj+prej1)mod108

d p i − 1 , j − 1 = d p i , j dp_{i-1,j-1}=dp_{i,j} dpi1,j1=dpi,j,那么说明 d p i − 1 , j − 1 dp_{i-1,j-1} dpi1,j1 会重复计算,则:

p a t h i , j = ( p a t h i , j − p r e j − 1 ) m o d 10 8 path_{i,j}=(path_{i,j}-pre_{j-1})\bmod 10^8 pathi,j=(pathi,jprej1)mod108

初始化:

i ∈ [ 0 , m ] , p r e i = 1 i ∈ [ 0 , n ] , p a t h i = 1 i\in[0,m],pre_i=1\\ i\in[0,n],path_i=1 i[0,m],prei=1i[0,n],pathi=1

注意: p a t h path path 数组每一层都需要清空。

实现

#include<bits/stdc++.h>
using namespace std;
const int mod=1e8;
string s,t;
int dp[5005][5005],n,m,path[5005],pre[5005];
signed main(){ios::sync_with_stdio(0),cin.tie(0),cout.tie(0);cin>>s>>t;n=s.size(),m=t.size();s=' '+s,t=' '+t;n--,m--;for(int i=1;i<=n;i++){for(int j=1;j<=m;j++){if(s[i]==t[j]){dp[i][j]=dp[i-1][j-1]+1;}else{dp[i][j]=max(dp[i-1][j],dp[i][j-1]);}}}for(int i=0;i<=m;i++){pre[i]=1;}for(int i=0;i<=n;i++){path[i]=1;}for(int i=1;i<=n;i++){for(int j=1;j<=m;j++){path[j]=0;if(dp[i][j]==dp[i-1][j]){path[j]=(path[j]+pre[j])%mod;}if(dp[i][j]==dp[i][j-1]){path[j]=(path[j]+path[j-1])%mod;}if(s[i]==t[j]&&dp[i][j]==dp[i-1][j-1]+1){path[j]=(path[j]+pre[j-1])%mod;}if(dp[i-1][j-1]==dp[i][j]){path[j]=((path[j]-pre[j-1])%mod+mod)%mod;}}for(int j=1;j<=m;j++){pre[j]=path[j];}}cout<<dp[n][m]<<'\n'<<path[m];return 0;
}

http://www.hkcw.cn/article/DJDWIDouaL.shtml

相关文章

Windows最快速打开各项系统设置大全

目录 一、应用背景 二、设置项打开方法 2.1 方法一界面查找&#xff08;最慢&#xff09; 2.2 方法二cmd命令&#xff08;慢&#xff09; 2.3 方法三快捷键&#xff08;快&#xff09; 2.4 方法四搜索栏&#xff08;快&#xff09; 2.5 方法五任务栏&#xff08;最快&am…

OTSU算法原理与Python实现:图像二值化的自动阈值分割

1 引言 图像二值化是计算机视觉中的基础操作&#xff0c;它将灰度图像转换为黑白图像&#xff0c;常用于文档扫描、目标检测等任务。OTSU算法&#xff08;大津法&#xff09;是一种自动确定二值化阈值的算法&#xff0c;无需人工干预&#xff0c;通过最大化类间方差来分离前景和…

python:批量创建文件

#需求&#xff1a;在指定路径下批量创建3000#可以先弄个10个文本文件&#xff0c;文件格式为序号——物资类别——用户识别码组成 #1.序号从0001到3000 #2.物资类别包括&#xff1a;水果&#xff0c;烟酒&#xff0c;粮油&#xff0c;肉蛋&#xff0c;蔬菜 #3.用户识别码为9位的…

kafka学习笔记(三、消费者Consumer使用教程——配置参数大全及性能调优)

本章主要介绍kafka consumer的配置参数及性能调优的点&#xff0c;其kafka的从零开始的安装到生产者&#xff0c;消费者的详解介绍、源码及分析及原理解析请到博主kafka专栏 。 1.消费者Consumer配置参数 配置参数默认值含义bootstrap.servers无&#xff08;必填&#xff09;…

静态综合实验

题目 1.划分IP地址 因为所有网段基于192.168.1.0/24&#xff0c;所以需要自己进行合理的划分。如图&#xff0c;我已经划分完成。 2.启动 3.给五个路由器进行改名 4.给网关写入IP地址 R1 R2 R3 R4 5.完成网段的声明和环回接口的创建 6.在R1上进行ping&#xff0c;观察是否…

流媒体基础解析:音视频封装格式与传输协议

在视频处理与传输的完整流程中&#xff0c;音视频封装格式和传输协议扮演着至关重要的角色。它们不仅决定了视频文件的存储方式&#xff0c;还影响着视频在网络上的传输效率和播放体验。今天&#xff0c;我们将深入探讨音视频封装格式和传输协议的相关知识。 音视频封装格式 什…

保持本地 Git 项目副本与远程仓库完全同步

核心目标&#xff1a; 保持本地 Git 项目副本与 GitHub 远程仓库完全同步。 关键方法&#xff1a; 定期执行 git pull 命令。 操作步骤&#xff1a; 进入项目目录&#xff1a; 在终端/命令行中&#xff0c;使用 cd 命令切换到你的项目文件夹。执行拉取命令&#xff1a; 运行…

Go语言的context

Golang context 实现原理 本篇文章是基于小徐先生的文章的修改和个人注解&#xff0c;要查看原文可以点击上述的链接查看 目前我这篇文章的go语言版本是1.24.1 context上下文 context被当作第一个参数&#xff08;官方建议&#xff09;&#xff0c;并且不断的传递下去&…

2025年全国青少年信息素养大赛复赛C++算法创意实践挑战赛真题模拟强化训练(试卷3:共计6题带解析)

2025年全国青少年信息素养大赛复赛C++算法创意实践挑战赛真题模拟强化训练(试卷3:共计6题带解析) 第1题:四位数密码 【题目描述】 情报员使用4位数字来传递信息,同时为了防止信息泄露,需要将数字进行加密。数据加密的规则是: 每个数字都进行如下处理:该数字加上5之后除…

NeRF PyTorch 源码解读 - 体渲染

文章目录 1. 体渲染公式推导1.1. T ( t ) T(t) T(t) 的推导1.2. C ( r ) C(r) C(r) 的推导 2. 体渲染公式离散化3. 代码解读 1. 体渲染公式推导 如下图所示&#xff0c;渲染图像上点 P P P 的颜色值 c c c 是累加射线 O P → \overrightarrow{OP} OP 在近平面和远平面范围…

Sentiment analysis integrating LangGraph and large-scale conceptual models

Sentiment analysis integrating LangGraph and large-scale conceptual models 核心目标&#xff1a; 让电脑更聪明地理解大量用户评论&#xff08;比如邮件、社交媒体、调查问卷&#xff09;&#xff0c;自动分析出大家是夸还是骂&#xff08;情感分析&#xff09;&#xff…

DeepSeek R1-0528:深度思考能力的重大跃升与技术突破全解析

引言 2025年5月28日&#xff0c;DeepSeek再次以其标志性的"深夜发布"方式&#xff0c;悄然推出了R1模型的最新版本——DeepSeek-R1-0528。这次被官方定义为"小版本升级"的更新&#xff0c;实际上带来了令人瞩目的性能提升。新版本不仅在数学、编程与通用逻…

Python 训练营打卡 Day 40

训练和测试的规范写法 一、黑白图片的规范写法&#xff0c;以MNIST数据集为例 import torch import torch.nn as nn import torch.optim as optim from torchvision import datasets, transforms # 用于加载MNIST数据集 from torch.utils.data import DataLoader # 用于创建…

题海拾贝:P8598 [蓝桥杯 2013 省 AB] 错误票据

Hello大家好&#xff01;很高兴我们又见面啦&#xff01;给生活添点passion&#xff0c;开始今天的编程之路&#xff01; 我的博客&#xff1a;<但凡. 我的专栏&#xff1a;《编程之路》、《数据结构与算法之美》、《题海拾贝》 欢迎点赞&#xff0c;关注&#xff01; 1、题…

AI炼丹日志-26 - crawl4ai 专为 AI 打造的爬虫爬取库 上手指南

点一下关注吧&#xff01;&#xff01;&#xff01;非常感谢&#xff01;&#xff01;持续更新&#xff01;&#xff01;&#xff01; Java篇&#xff1a; MyBatis 更新完毕目前开始更新 Spring&#xff0c;一起深入浅出&#xff01; 大数据篇 300&#xff1a; Hadoop&…

homework 2025.03.31 chinese(class 3)

homework 2025.03.31 chinese&#xff08;class 3&#xff09; 三年级语文&#xff0c;古代十二时辰 ➠1. 子时&#xff08;23-1时&#xff09; “月落乌啼霜满天&#xff0c;江枫渔火对愁眠。姑苏城外寒山寺&#xff0c;夜半钟声到客船。” — 张继《枫桥夜泊》 子时夜深人静&…

若依框架定制化服务搭建

1.背景 若依框架是1套微服务框架&#xff0c;该服务在应用过程中少不了新增微服务来应对业务的需求&#xff0c;本次文档主要是针对若依框架的定制化微服务的搭建进行步骤的拆解。 2.ruoyi-api模块新建模块【report】 2.1 右键【ruoyi-api】&#xff0c;New一个Module 2.2 新…

【HW系列】—溯源与定位—Linux入侵排查

文章目录 一、Linux入侵排查1.账户安全2.特权用户排查&#xff08;UID0&#xff09;3.查看历史命令4.异常端口与进程端口排查进程排查 二、溯源分析1. 威胁情报&#xff08;Threat Intelligence&#xff09;2. IP定位&#xff08;IP Geolocation&#xff09;3. 端口扫描&#x…

JS入门——变量的类型、特殊符号、类型转化规则

JS入门——变量的类型、特殊符号、类型转化规则 一、变量类型 1.1总述 1.2代码实现 <!DOCTYPE html> <html><head><meta charset"utf-8"><title></title></head><body><script>// tyoeo可以检测出类型aler…

手写HashMap

项目仓库&#xff1a;https://gitee.com/bossDuy/hand-tear-collection-series 基于一个b站up的课程&#xff1a;https://www.bilibili.com/video/BV1SWZrYDEag?spm_id_from333.788.videopod.sections&vd_source4cda4baec795c32b16ddd661bb9ce865 手写简单的HashMap 这里…