2015.9.2组队赛 1006题(dp:前缀和优化,空间压缩)

题目描述

求有多少个数列满足a1+a2+a3+...+am=n, 0<a1<a2<a3<…<am

输入描述

第一行是样例个数。(少于10000个样例)

每一个样例第一行有n,m(1<=m<=n<=10^5)

输出描述

每一个样例,输出答案mod 1000000007

输入

2

4 2

4 3

输出

1

0

思路:首先我们容易想到要想有一个数列满足条件,那么n>=(m*(m+1)/2)。所以我们只需要把m控制在sqrt(n)的范围内就够了。超出范围的ans就是0。然后我们令dp[n][m]表示答案,那么状态转移方程为:

dp[n][m] = dp[n - m][m - 1] + dp[n - 2 * m][m-1]+...
就是把全部的满足要求的数列按照a1的数值分类。第一类就是a1=1,那么a2...am必定都>1,所以这一类的个数为dp[n-m][m-1]。其他的类推就好了。

然后因为状态转移方程比较复杂,我们用前缀和优化一下。令sum[u]表示u之前间隔m的前缀和,即

sum[u]= dp[u]+dp[u-m]+dp[u-2*m]+dp[u-3*m]+...
空间压缩就是dp二维简单的变为一维了。。。(压缩空间之后就要离线做了)

#pragma warning(disable:4996)
#include <cstdio>
#include <cstring>
#include <algorithm>
using namespace std;
const int N = 100005;
const int mod = 1000000007;
struct Q{
	int n, m, id;
	bool operator<(const Q&a)const{
		if (m == a.m)return n < a.n;
		return m < a.m;
	}
};

int dp[N], sum[N];
//sum[u]表示前u项,,间隔m的和,即:sum[u]= dp[u]+dp[u-m]+dp[u-2*m]+dp[u-3*m]+...
Q q[10005];
int ans[10005];

int main(){
	int t; scanf("%d", &t);
	int n, m;
	for (int i = 1; i <= t; i++){
		scanf("%d %d", &n, &m);
		q[i].n = n; q[i].m = m; q[i].id = i;
	}

	sort(q + 1, q + 1 + t);

	//因为初始的sum数组是用来算m=1的,这时候所有的dp[n]=1,
	//所以初始化sum数组为1是可行的
	//for (int i = 0; i < N; i++)sum[i] = 1;

	int cnt = 1;
	for (m = 1; m < 450; m++){
		//或者不初始化sum数组,可以把m=1的情况单独拿出来
		if (m == 1){
			for (n = 1; n < N; n++)dp[n] = 1;
		}
		else{
			for (n = 1; n < N; n++){
				if (n >= m)dp[n] = sum[n - m];
				else dp[n] = 0;
			}
		}
		//填充询问m的答案
		while (cnt <= t&&q[cnt].m == m){
			ans[q[cnt].id] = dp[q[cnt].n];
			cnt++;
		}
		//重新计算sum
		for (n = 0; n <= m; n++)sum[n] = dp[n];
		for (n = m + 1; n < N; n++)
			sum[n] = (sum[n - (m + 1)] + dp[n]) % mod;
	}
	//其他的m肯定比450大,,答案都是0
	while (cnt <= t){
		ans[q[cnt].id] = 0;
		cnt++;
	}
	for (int i = 1; i <= t; i++)printf("%d\n", ans[i]);
	return 0;
}


  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值