蓝桥杯-波动数列
观察这个数列:
1 3 0 2 -1 1 -2 …
这个数列中后一项总是比前一项增加2或者减少3,且每一项都为整数。
栋栋对这种数列很好奇,他想知道长度为 n 和为 s 而且后一项总是比前一项增加 a 或者减少 b 的整数数列可能有多少种呢?
输入格式
共一行,包含四个整数 n,s,a,b,含义如前面所述。
输出格式
共一行,包含一个整数,表示满足条件的方案数。
由于这个数很大,请输出方案数除以 100000007 的余数。
数据范围
1≤n≤1000,
−1e9≤s≤1e9,
1≤a,b≤1e6
输入样例:
4 10 2 3
输出样例:
2
样例解释
两个满足条件的数列分别是2 4 1 3和7 4 1 -2。
题解:
化简等式:
设第一个数是x, d等于a或b)
x, x + d1, x + d1 + d2 ... (x +...+ d(n - 1)) == s
=> (n) * x + (n - 1) * d1 + (n - 2) * d2 ... 1 * d(n - 1) = s
=> x = (s - (n - 1) * d1 - (n - 2) * d2 ... 1 * d(n - 1)) / n
由于 x 只要是整数就行, 所以只要 (s - (n - 1)d1 - (n - 2)d2 ... 1xd(n - 1))是 n 的倍数就行, 也就是 s % n == ( (n - 1)d1 + (n - 2)d2 ... 1xd(n - 1) ) % n
这里有个结论: 两个整数数 a, b. 如果 a % k == b % k, 那么说明 abs(a - b)一定是n的倍数。 蓝桥杯也出一个相关的题目 -> k倍区间
dp分析
常见疑惑: 为什么是状态计算是f[i][j] = f[i - 1][j - (n - i) * a] + f[i - 1][j + (n - i)b]
- 看到这里, 你要理解,等式左边f[i][j]中的 j是第i个选择的是a或b的序列的和对 n 取模后的余数, 这个序列里面是不包含初始值x的, 这也是为什么 i是[1,n),只循环了n - 1次,
(一共n个数, 我们只需要选n - 1次) - 观察等式
(n - 1) * d1 + (n - 2) * d2 ... 1 * d(n - 1) % n = j
可以发现 从左往右 第 i 项(不含第i项)的系数是 (n - i), 那么前 i 项的和应该是 j - (n - i)a 或 j + (n - i)b(d是a或-b)
要理解转移这个思想, 从 i - 1, j - (n - i) * a 、 j + (n - i)b 转移到f[i][j]
ac代码👇
#include <bits/stdc++.h>
using namespace std;
const int N = 1e3 + 10, MOD = 100000007;
int f[N][N];
int get_mod(int a, int b) //由于a%b可能是负数, 我们要的是正数, 这个函数可以实现
{
return (a % b + b) % b;
}
int main()
{
int n, s, a, b; cin >> n >> s >> a >> b;
f[0][0] = 1;
for (int i = 1; i < n; i ++) // 只选择 i - 1次数 (长度为n的序列, 只有 n - 1个间隙)
for (int j = 0; j < n; j ++) // j - (n - i)a是前i项的和, get_mod是让其与 n 相除取余数
f[i][j] = f[i - 1][get_mod(j - (n - i) * a, n)] % MOD + f[i - 1][get_mod(j + (n - i) * b, n)] % MOD;
cout << f[n - 1][get_mod(s, n)] % MOD << endl;
return 0;
}