标题:波动数列
观察这个数列:
1 3 0 2 -1 1 -2 ...
这个数列中后一项总是比前一项增加2或者减少3。
栋栋对这种数列很好奇,他想知道长度为 n 和为 s 而且后一项总是比前一项增加a或者减少b的整数数列可能有多少种呢?
【数据格式】
输入的第一行包含四个整数 n s a b,含义如前面说述。
输出一行,包含一个整数,表示满足条件的方案数。由于这个数很大,请输出方案数除以100000007的余数。
例如,输入:
4 10 2 3
程序应该输出:
2
【样例说明】
这两个数列分别是2 4 1 3和7 4 1 -2。
【数据规模与约定】
对于10%的数据,1<=n<=5,0<=s<=5,1<=a,b<=5;
对于30%的数据,1<=n<=30,0<=s<=30,1<=a,b<=30;
对于50%的数据,1<=n<=50,0<=s<=50,1<=a,b<=50;
对于70%的数据,1<=n<=100,0<=s<=500,1<=a, b<=50;
对于100%的数据,1<=n<=1000,-1,000,000,000<=s<=1,000,000,000,1<=a, b<=1,000,000。
资源约定:
峰值内存消耗 < 256M
CPU消耗 < 1000ms
请严格按要求输出,不要画蛇添足地打印类似:“请您输入...” 的多余内容。
所有代码放在同一个源文件中,调试通过后,拷贝提交该源码。
注意: main函数需要返回0
注意: 只使用ANSI C/ANSI C++ 标准,不要调用依赖于编译环境或操作系统的特殊函数。
注意: 所有依赖的函数必须明确地在源文件中 #include <xxx>, 不能通过工程设置而省略常用头文件。
提交时,注意选择所期望的编译器类型。
首先,要发现这个数列的规律。假设首项就为x,那么第二项可以是x+a,x-b,假如第二项为x+a,那么第三项就是在第二项的基础上+a或-b,即x+a+a或x+a-b。所以直到最后一项,就是x+x+p+x+2p+……+x+(n-1)p,这里的p表示+a或-b,即每一次的p都有2种选择的可能。
由此我们可以发现,只要我们枚举a的个数,求出对应情况下的总和,若是s减去这个总和恰好是n的倍数,就说明此时ab的组合方法符合题目要求。结果就累加此时放置这么多a的方法数。所以接下来的问题就是求出,在a每次加1到加n的情况下,总数恰好为此时a的个数的方案数。
于是就可以想象成01背包。有编号为1-n的n个物品,每个物品的重量分别是1-n,dp[i][j]表示用前i件物品恰好塞满容量j的背包的方案数 。
#include<stdio.h>
#include<string.h>
#define mod 100000007
using namespace std;
long long dp[2][500005];//n*(n+1)/2
int e, n,a, b;
long long sum = 0, s;
void solve()//想象成01背包。有编号为1-n的n个物品,每个物品的重量分别是1-n,dp[i][j]表示用前i件物品恰好塞满容量j的背包的方案数
{
int i, j;
dp[e][0] = 1;
dp[e][1] = 1;
for(i = 2 ; i < n ; i++)
{
e = 1 - e;//滚动数组。因为每次都只用到上一层的dp,所以dp只保留两层即可
for(j = 0 ; j <= (i + 1) * i / 2 ; j++)
{
if(i > j)
dp[e][j] = dp[1 - e][j];
else
dp[e][j] = (dp[1 - e][j] + dp[1 - e][j - i]) % mod;
}
}
}
int main()
{
long long r;
int i;
scanf("%d %lld %d %d", &n, &s, &a, &b);
solve();
for(i = 0 ; i <= (n - 1) * n / 2 ; i++)//可供选择的a的个数最大有(1+2+……+(n-1))个,分别对应在第二个数上+a,在第三个数上+a……在第n个数上+a
{
r = s - i * a + (n * (n - 1) / 2 - i) * b;//算出i个a的情况下的剩余值,如果符合条件,则剩余值必定是n的倍数,系数就是首项
if(r % n == 0)
sum = (sum + dp[e][i]) % mod;
}
printf("%lld\n", sum);
return 0;
}