bzoj 2423: [HAOI2010]最长公共子序列 (DP)

本文介绍了一种求解最长上升子序列及其数量的方法,并通过动态规划实现了算法。文章详细解释了状态转移方程和计数原理,最后给出了完整的代码实现。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

题目描述

传送门

题目大意:求最长上升子序列及个数

题解

第一问O(n^2)直接转移就可以。
f[i][j]=max(f[i1][j],f[i][j1])  s[i]!=s1[j]
f[i][j]=f[i1][j1]+1  s[i]=s1[j]
这些都比较容易,关键是怎么求方案数呢?
如果 g[i][j] 也按照上述方式去推,就是相等的时候直接继承;不等的时候如果 f[i][j1]=f[i1][j] 就加和,否则继承较大的。这样计算出的结果是所有本质不同的串的个数。
如何求个数?当 s[i]=s1[j] 时,发现 f[i][j] 更新后面的时候只用到了路径上的信息,如果 f[i1][j]=f[i][j] 的话后面接的时候其实也可以从 f[i1][j] 后面接,也就是 f[i1][j1]+1 是新产生的,而 f[i1][j],f[i][j1] 相当于是求前缀和的样子。会不会算重复呢? f[i1][j1] 不可能去更新 f[i1][j],f[i][j1] ,所以新产生的路径一定与之前的没有交集。
s[i]!=s1[j] 时,如果 g[i][j]=g[i1][j]+g[i][j1] 那么 g[i1][j1] 相当于被计算了两次,还需要减掉一次。

代码

#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<cmath>
#define N 5003
#define p 100000000
using namespace std;
char s[N],s1[N];
int f[3][N],g[3][N],n,m;
int main()
{
    freopen("a.in","r",stdin);
    scanf("%s",s+1); n=strlen(s+1);
    scanf("%s",s1+1); m=strlen(s1+1);
    g[0][0]=1; n--; m--;
    for (int j=1;j<=m;j++) g[0][j]=1;
    g[1][0]=1;
    for (int i=1;i<=n;i++){
     int t=i&1; int t1=(i-1)&1;
     for (int j=1;j<=m;j++){
        if (s[i]==s1[j]) {
          f[t][j]=f[t1][j-1]+1;
          g[t][j]=g[t1][j-1];
          if (f[t][j]==f[t1][j]) (g[t][j]+=g[t1][j])%=p;
          if (f[t][j]==f[t][j-1]) (g[t][j]+=g[t][j-1])%=p;
        }
        else {
            f[t][j]=max(f[t][j-1],f[t1][j]);
            g[t][j]=0;
            if (f[t][j]==f[t1][j-1]) (g[t][j]-=g[t1][j-1])%=p;
            if (f[t][j]==f[t1][j]) (g[t][j]+=g[t1][j])%=p;
            if (f[t][j]==f[t][j-1]) (g[t][j]+=g[t][j-1])%=p;
         }
     }
    }
    printf("%d\n%d\n",f[n&1][m],(g[n&1][m]%p+p)%p);
}
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值