蓝桥杯-波动数列

观察这个数列:

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;
}
posted @ 2024-05-11 11:34  小程xy  阅读(16)  评论(0编辑  收藏  举报