【JZOJ1857】最大值【dp】

题目大意:

题目链接:
JZOJ:https://jzoj.net/senior/#main/show/1857
学校局域网:http://10.156.31.134/contestnew.aspx?cid=91

求在1m1\sim m中选出nn个(可重复)最长严格上升子序列为p+1p+1的方案数。


思路:

由于TT比较大,考虑先预处理。
f[i][j][k]f[i][j][k]表示前ii个数字,第ii个数字为jj,最长上升子序列长度为kk的方案数。
显然需要枚举一个ll,表示第i1i-1位选择的数字。
如果此次选择的数字是最长上升子序列的最后一位(也就是jj),那么第i1i-1位就可能是1j11\sim j-1的任意一位。所以有
f[i][j][k]+=f[i1][l][k1]f[i][j][k]+=f[i-1][l][k-1]
如果此次选择的数字不是最长上升子序列的最后一位,那么在1i11\sim i-1中最大值也是jj,这次选择的数字是在1j1\sim j中的任意一个数字,那么就有
f[i][j][k]+=f[i1][j][k]×jf[i][j][k]+=f[i-1][j][k]\times j
但是这样的时间复杂度是O(n2k2)O(n^2k^2)的。
考虑把枚举的ll去掉。
由于我们要求
l=1j1f[i1][k][k1]\sum^{j-1}_{l=1}f[i-1][k][k-1]
所以我们可以考虑使用前缀和优化。
g[i][j][k]=l=1jf[i][j][k]g[i][j][k]=\sum^{j}_{l=1}f[i][j][k],那么第一条方程就变成了
f[i][j][k]+=g[i1][j1][k1]f[i][j][k]+=g[i-1][j-1][k-1]
这样的时间复杂度就是O(n2k)O(n^2k)的了。
接下来TT次询问,每次枚举一下最长上升子序列的最后一位是几,然后取最优输出即可。


代码:

#include <cstdio>
using namespace std;
typedef long long ll;

const int MOD=1e9+7,N=100,M=300;
ll f[N+10][M+10][N+10],g[N+10][M+10][N+10],ans;
int T,n,m,p;

int main()
{
	scanf("%d",&T);
	for (int i=1;i<=M;i++)
		f[1][i][1]=1,g[1][i][1]=i;
	for (int i=2;i<=N;i++)
		for (int j=1;j<=M;j++)
			for (int k=1;k<=i;k++)
			{
				f[i][j][k]=(g[i-1][j-1][k-1]+f[i-1][j][k]*(ll)j)%MOD;
				g[i][j][k]=(f[i][j][k]+g[i][j-1][k])%MOD;
			}
	while (T--)
	{
		scanf("%d%d%d",&n,&m,&p);
		ans=0;
		for (int i=p+1;i<=m;i++)
			ans=(ans+f[n][i][p+1])%MOD;
		printf("%lld\n",ans);
	}
	return 0;
}
posted @ 2019-04-13 07:44  全OI最菜  阅读(114)  评论(0编辑  收藏  举报