把博客园图标替换成自己的图标
把博客园图标替换成自己的图标end

【洛谷2516】[HAOI2010] 最长公共子序列(动态规划)

点此看题面

  • 给定两个串,求最长上升子序列长度及方案数。
  • \(n\le5\times10^3\)

最长上升子序列

显然,设\(f_{i,j}\)表示第一个串匹配到第\(i\)位,第二个串匹配到第\(j\)位时的最长上升子序列长度。

套路\(DP\)得到\(f_{i,j}=\max\{f_{i-1,j-1}+[s1_i=s2_j],f_{i-1,j},f_{i,j-1}\}\)

然后考虑\(g_{i,j}\),第一个想法是类似地从这三个地方转移,但实际上是有问题的。

\(f_{i-1,j-1}+[s1_i=s2_j]\)是有创新意义的改革,没啥问题;但从\(f_{i-1,j}\)\(f_{i,j-1}\)的转移相当于会重复\(f_{i-1,j-1}\)的转移,需要改进。

类二维前缀和

发现这个式子长得很像二维前缀和。

所以我们只要从\(f_{i-1,j}\)\(f_{i,j-1}\)转移后再减去一次\(f_{i-1,j-1}\)的贡献即可。

或许你会奇怪,最长上升子序列的贡献怎么减去。

但实际上因为我们只是减去重复计算的部分,最长上升子序列的长度不会改变,只需要在长度相等的时候减去方案数即可。

代码:\(O(n^2)\)

#include<bits/stdc++.h>
#define Tp template<typename Ty>
#define Ts template<typename Ty,typename... Ar>
#define Reg register
#define RI Reg int
#define Con const
#define CI Con int&
#define I inline
#define W while
#define N 5000
#define X 100000000
#define R(x) (1LL*rand()*rand()*rand()%(x)+1)
using namespace std;
int n,m;char s1[N+5],s2[N+5];
struct Data
{
	int v,c;I Data(CI a=0,CI b=0):v(a),c(b){}
	I Data operator + (CI x) Con {return Data(v+x,c);}
	I Data operator + (Con Data& o) Con {return v^o.v?(v>o.v?*this:o):Data(v,(c+o.c)%X);}//长度不同时取较大值,相同时合并方案数
	I friend void operator += (Data& x,Con Data& y) {x=x+y;} 
	I friend void operator -= (Data& x,Con Data& y) {x=x+Data(y.v,X-y.c);}
}f[2][N+5];
int main()
{
	RI i,j;scanf("%s%s",s1+1,s2+1),n=strlen(s1+1)-1,m=strlen(s2+1)-1;
	for(i=0;i<=n;++i) for(j=0;j<=m;++j)//有意义的转移直接搞,否则类二维前缀和转移
		f[i&1][j]=Data(0,!i&&!j),i&&(f[i&1][j]+=f[i&1^1][j],0),j&&(f[i&1][j]+=f[i&1][j-1],0),
		i&&j&&(s1[i]==s2[j]?f[i&1][j]+=f[i&1^1][j-1]+1:f[i&1][j]-=f[i&1^1][j-1],0);
	return printf("%d\n%d\n",f[n&1][m].v,f[n&1][m].c),0;
}
posted @ 2021-04-03 06:49  TheLostWeak  阅读(42)  评论(0编辑  收藏  举报