hiho一下第六十周

19 篇文章 0 订阅
11 篇文章 0 订阅

题目大意:给出两个字符串,求出满足下列条件的最长的公共子序列:

①公共子序列连续的部分长度>=3

②断开处单调递增

分析:看起来好像经典问题最长公共子序列——但是不是。一开始想到直接写转移方程,发现如果前面长度为2,1之类,即使和后面连起来长度大于3了,这种状态也不能被找到;又想到先求最长公共子序列,然后减去小于3 的段,但是发现不仅程序中定位断开部分难写,而且会产生错解。

所以接着第一个思路,dp[i][j]之前连的串一定是一个连续公共的,并且我们可以选择它的长度为3..maxlength

预处理f[i][j]表示以a[i] b[j]结尾的串的公共后缀长度(二维DP)

然后开始状态转移,要注意的是开0 1两维,0表示这一位不一定要选的最大值,1表示这一位一定要选的最大值(前提是能选,不能的话,dp[i][j][1]初始为0)

关于枚举要连接的长度,这可能让复杂度达到O(N^3)

看了题解感觉真心巧妙:dp[i][j]=max(dp[i-k][j-k]+k)    (f[i][j]=>k>=3)

即dp[i][j]=max(dp[i-3][j-3]+3,dp[i-4][j-4]+4,...,dp[i-f[i][j]][j-f[i][j]]+f[i][j])

考虑i-1 

dp[i-1][j-1]=max(dp[i-1-3][j-1-3]+3, dp[i-1-4][j-1-4]+3, ... , dp[i-1-f[i-1][j-1]][j-1-f[i-1][j-1]]+f[i-1][j-1])

f[i][j]=f[i-1][j-1]+1   (当 a[i]=b[j]时)

dp[i-1][j-1]=max(dp[i-4][j-4]+3, dp[i-5][j-5]+4, ... ,dp[i-f[i][j]][j-f[i][j]]+f[i][j]-1)

所以dp[i][j]=max(dp[i-3][j-3]+3, dp[i-1][j-1]+1)

然后判断即可

减少DP中找决策的维数(减少循环层数)

这里利用了展开找max 或min的规律,以达到减少重复寻找断开位置的循环

DP中好像还有也要枚举分开的位置,进行加减转移的最大值,为了减少这个循环层数,可以将所有点分开位置的枚举转移放到最后的那个点一起转移。


#include <iostream>
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <string>
#include <cmath>
using namespace  std;
string a,b;
int la,lb;
int f[2100][2100];
int dp[2100][2100][1];
int main()
{
   cin>>a;
   cin>>b;
   la=a.size();
   lb=b.size();
   a=' '+a;
   b=' '+b;
   for (int i=1;i<=la;i++)
    for (int j=1;j<=lb;j++)
      if (a[i]==b[j])
        f[i][j]=f[i-1][j-1]+1;
      else
        f[i][j]=0;
   for (int i=1;i<=la;i++)
    for (int j=1;j<=lb;j++)
    {
       dp[i][j][1]=0;
       if (f[i][j]>=3)
        {
            dp[i][j][1]=dp[i-3][j-3][0]+3;
            if (f[i][j]>3)
                dp[i][j][1]=max(dp[i][j][1],dp[i-1][j-1][1]+1);
        }
       dp[i][j][0]=max(dp[i-1][j][0],max(dp[i][j-1][0],dp[i][j][1]));
    }
   cout<<dp[la][lb][0];
   return 0;
}<span style="color:#ff6600;">
</span>



  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值