LIS和LCS的一些个人体会
LIS
俗名最长递增子序列,一个dp问题。
最简单的解法是时间复杂度O(n2)的解法,优化版的是二分的O(nlogn)解法。
若仅仅只是求长度
int LIS(vector<int>& arr) {
// write code here
int n=arr.size();
vector<int>res;
for(int i=0;i<n;i++)
{
auto it=lower_bound(res.begin(),res.end(),arr[i]);
if(it==res.end())
{
res.push_back(arr[i]);
}
else
{
*it=arr[i];
}
}
return res.size();
}
若是要求求LIS的具体元素,就需要额外引进一个数组pos,用来标记LIS每个元素都是谁,然后根据这个pos数组,用一个指针等于LIS的长度,倒着遍历,遇到一个元素的pos值与p相等,说明LIS的第p个元素就是他,计入答案数组即可,别忘了记录完p--。
vector<int> LIS(vector<int>& arr) {
int n=arr.size();
int len=0;
vector<int>pos(n);
vector<int>dp;
for(int i=0;i<n;i++)
{
auto it=lower_bound(dp.begin(),dp.end(),arr[i]);
pos[i]=it-dp.begin();
if(it==dp.end())
{
dp.push_back(arr[i]);
}
else
{
*it=arr[i];
}
}
int p=dp.size()-1;
vector<int> ans(p+1);
for(int i=n-1;i>=0;i--)
{
if(p==pos[i])
{
ans[p]=(arr[i]);
p--;
}
}
return ans;
LCS
又叫最长公共子序列,就是找到两个字符串的LCS。
1.求LCS的长度
把两个字符串看成一个二维矩阵,会发现这其实就是一个二维dp,如果s1[i-1]==s2[j-1],那么dp[i][j]就等于dp[i-1][j-1]+1,如果不相等就需要从这个dp[i][j]的上方或者左方去找一个较大值
dp[i][j]=max(dp[i-1][j],dp[i][j-1]);这样计算下来dp[n][m]就是LCS的数值。
2.求LCS的具体字符串
需要在计算完dp[n][m]后,接着对两个字符串用两个指针倒序遍历,如果s[l-1]==s[r-1],那么说明这个字符就是我们想要的,用一个字符串收集起来。如果不相等呢,我们需要去看这个dp[l][r]上和左的情况,我们要去找更大的情况,举个例子如果左边大于上边,就需要r--。
string LCS(string s1, string s2) {
int n = s1.size(), m = s2.size();
vector<vector<int>> dp(n + 1, vector<int>(m + 1));
for (int i = 1; i <= n; i++)
{
for (int j = 1; j <= m; j++)
{
if (s1[i - 1] == s2[j - 1])
{
dp[i][j] = dp[i - 1][j - 1] + 1;
}
else
{
dp[i][j] = max(dp[i - 1][j], dp[i][j - 1]);
}
}
}
string str="";
int l=n,r=m;
while(l>0&&r>0)
{
if(s1[l-1]==s2[r-1])
{
str=s1[l-1]+str;
l--;
r--;
}
else if(dp[l-1][r]<dp[l][r-1])
{
r--;
}
else
{
l--;
}
}
if(str=="")return "-1";
else return str;
}
3.求所有LCS字符串的个数
另外开辟一个c数组来计算方案数,如果dp[i][j]dp[i-1][j]那么dp[i-1][j]的方案就需要收集,如果dp[i][j]dp[i][j-1]那么dp[i][j-1]的方案就需要收集,如果同时收集了就说明dp[i-1][j-1]的方案收集了两次,需要再减去一次。
void solve()
{
cin >> s >> t;
n = s.size()-1, m = t.size()-1;
dp.assign(n + 1, vi(m + 1, 0));
vvi c(n + 1, vi(m + 1, 0));
// 初始化
for(int i=0; i<=n; i++) c[i][0] = 1;
for(int i=0; i<=m; i++) c[0][i] = 1;
for (int i = 1; i <= n; i++)
{
for (int j = 1; j <= m; j++)
{
if (s[i-1] == t[j-1])
{
dp[i][j] = dp[i - 1][j - 1] + 1;
// 当字符相等时,最长公共子序列必定包含这对字符
// 方案数直接继承左上方
// 题目要求的是不同位置的序列,所以要按如下方式处理:
if(dp[i][j] == dp[i-1][j]) c[i][j] = (c[i][j] + c[i-1][j]) % mod;
if(dp[i][j] == dp[i][j-1]) c[i][j] = (c[i][j] + c[i][j-1]) % mod;
}
else
{
dp[i][j] = max(dp[i - 1][j], dp[i][j - 1]);
// 方案数清零开始累加
c[i][j] = 0;
if(dp[i][j] == dp[i-1][j]) c[i][j] = (c[i][j] + c[i-1][j]) % mod;
if(dp[i][j] == dp[i][j-1]) c[i][j] = (c[i][j] + c[i][j-1]) % mod;
// 容斥去重:如果左和上长度都等于当前最大长度,说明左上角被加了两遍
if(dp[i][j] == dp[i-1][j-1])
{
c[i][j] = (c[i][j] - c[i-1][j-1] + mod) % mod;
}
}
}
}
cout << dp[n][m] << endl;
cout << c[n][m] << endl;
}
这个方法还是太吃空间了,不如转换为一维数组。
void solve()
{
string t;
cin >> s >> t;
n = s.size() - 1, m = t.size() - 1;
vi dp(m + 1, 0);
vi pre(m + 1, 0);
vi c(m + 1, 0);//上一行
vi p(m + 1, 1);//上一行
for (int i = 1; i <= n; i++)
{
dp[0] = 0;//空串相同字符为0
c[0] = 1;//空串反案为1
for (int j = 1; j <= m; j++)
{
if (s[i - 1] == t[j - 1])
{
dp[j] = pre[j - 1] + 1;
c[j] = p[j - 1];
if (dp[j] == pre[j])
c[j] = (c[j] + p[j]) % mod;
if (dp[j] == dp[j - 1])
c[j] = (c[j] + c[j - 1]) % mod;
}
else
{
dp[j] = max(pre[j], dp[j - 1]);
c[j] = 0;
if (dp[j] == pre[j])
c[j] = (c[j] + p[j]) % mod;
if (dp[j] == dp[j - 1])
c[j] = (c[j] + c[j - 1]) % mod;
if (dp[j] == pre[j - 1])
c[j] = (c[j] - p[j - 1] + mod) % mod;
}
}
pre = dp;
p = c;
}
cout << dp[m] << endl;
cout << c[m] << endl;
}
4.求所有具体方案
直接把n,m,dp[n][m]传进去然后dfs就行了,如果dp[i][j-1]==dp[i-1][j],说明两边都要dfs,其他就是一边dfs,代码就不写了()。

浙公网安备 33010602011771号