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