【BZOJ】2431 [HAOI2009]逆序对数列 递推

本文介绍了一种递推题目的优化方法,通过引入前缀和将时间复杂度从O(n^3)降至O(n^2),并讨论了使用滚动数组减少内存占用的方法。此外,还提供了一种公式改进的思路,简化递推过程。

题目传送门

不知道今天是怎么了,可能是空调吹多了吧,一直不在状态,连递推题我都做不来了……(扎Zn了老Fe……)

然而,不管环境如何恶劣,我们仍要努力学习,为了自己的明天而奋斗。(说的好像跟真的一样)

其实这题就是一个递推,现在我们考虑第i个数,定义f[i][j]表示序列里有i个数,逆序对的组数为j的方案数。

因为第i个数的权值就是i,则不管第i个数插到序列里的哪个位置,都会和在它后面的数形成逆序对,因此第i个数插到序列里最多形成i-1个逆序对,最少形成0个。

所以,我们就得到了递推公式:f[i][j]=Σf[i-1][j-k] (j-i+1<=k<=j)

但是现在的时间复杂度仍然是O(n^3)的,n的范围是1000,铁定TLE。

但是看到上面的递推式中有Σ,于是我们就想到了前缀和,降掉一维的复杂度,过掉这道题非常轻松。

另外,由递推式可发现,第i个数的所有逆序对方案数都只跟第i-1个数的逆序对方案数有关,因此可以使用滚动数组来存储,减少内存的使用。

(虽然在这题里并没有什么卵用,在BZOJ上实测出来大概省了80+kb的空间吧……)

注意:本题需要考虑中途答案为负的情况,虽然只要加上p就行了,但是一定要注意考虑,别忘了。

O(n^3)算法(主要是怕自己会忘):

#include <cstdio>
#define p 10000
using namespace std;

int n,m,f[1010][1010];

int main(void){
	scanf("%d%d",&n,&m);
	for (int i=1; i<=n; ++i) f[i][0]=1;
	for (int i=1; i<=n; ++i)
		for (int j=1; j<=m; ++j)
			for (int k=0; k<=j&&k<i; ++k)
				f[i][j]=(f[i][j]+f[i-1][j-k])%p;
	printf("%d",f[n][m]);
	return 0;
}

O(n^2)算法(AC代码):

#include <cstdio>
#define p 10000
using namespace std;

int n,m,f[1010],c[1010];

int main(void){
	scanf("%d%d",&n,&m);
	f[0]=1;
	for (int i=1; i<=n; ++i){
		for (int j=0; j<=m; ++j) c[j]=(c[j-1]+f[j])%p;
		for (int j=0; j<=m; ++j)
			if (j>0) if (j>=i) f[j]=(c[j]-c[j-i]+p)%p;
					else f[j]=c[j];
				else f[j]=1;
	}
	printf("%d",f[m]);
	return 0;
}
附:公式改进法,我在洛谷上看见的。

由上面的那个递推公式可知:f[i][j]=f[i-1][j]+f[i-1][j-1]+...+f[i-1][j-i+1]

f[i][j-1]=f[i-1][j-1]+f[i-1][j-2]+...+f[i-1][j-i]

所以f[i][j]=f[i-1][j]+f[i][j-1]-f[i-1][j-i]

虽然这个公式的变形在这题中并没有什么特别大的用处,但是这种思想是非常好的,常常可以把一些非常复杂的公式变得简单些,公式的特点也更明显一些。

所以我们还是有必要学习一下这种思想的。

然后就是递推了,其他都和上面的代码差不多的。

#include <cstdio>
#define p 10000
using namespace std;

int n,m,f[2][10010];

int main(void){
	scanf("%d%d",&n,&m);
	f[0][0]=1;
	for (int i=1; i<=n; ++i)
		for (int j=0; j<=m; ++j)
			if (j>0) if (j>=i) f[i%2][j]=(f[(i-1)%2][j]+f[i%2][j-1]-f[(i-1)%2][j-i]+p)%p;
						else f[i%2][j]=(f[(i-1)%2][j]+f[i%2][j-1])%p;
				else f[i%2][j]=(f[(i-1)%2][j])%p;
	printf("%d",f[n%2][m]>0?f[n%2][m]:-1);
	return 0;
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值