题目描述
求有多少个数列满足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;
}