AcWing 1214. 波动数列 题解

AcWing 1214. 波动数列

原题链接:AcWing 1214. 波动数列

题目描述

观察这个数列:

1 3 0 2 -1 1 -2 …

这个数列中后一项总是比前一项增加2或者减少3,且每一项都为整数。

栋栋对这种数列很好奇,他想知道长度为 n 和为 s 而且后一项总是比前一项增加 a 或者减少 b 的整数数列可能有多少种呢?

输入格式
共一行,包含四个整数 n,s,a,b,含义如前面所述。

输出格式
共一行,包含一个整数,表示满足条件的方案数。

由于这个数很大,请输出方案数除以 100000007 的余数。

数据范围
1≤n≤1000,
−109≤s≤109,
1≤a,b≤106
输入样例:

4 10 2 3

输出样例:

2
样例解释
两个满足条件的数列分别是2 4 1 3和7 4 1 -2。

题目分析

1.初步分析
首先,初值范围未知,只知道是一个整数
可以设数列首项为x, 第i次操作为pi(pi属于{+a, -b});
则数列为: x, x+p1, x+p1+p2, …, x+p1+p2+…+p(n-1)

2.问题转化
数列的和为: n*x+(n-1)*p1+(n-2)*p2+…+p(n-1) = s
即: x = {s - [(n-1)*p1+(n-2)*p2+…+p(n-1)] }/n
x为整数,故 {s - [(n-1)*p1+(n-2)*p2+…+p(n-1)] }%n = 0
即s 与 [(n-1)*p1+(n-2)*p2+…+p(n-1)] 模n同余,共n-1项的和

3.寻找转移方程
这样通过模n将每种状态的数据范围限制在n以内,可以用动态规划解决
所以可以用f[i][j]表示前i项数列组成的和si%n为j时的方案数
而f[i][j] 可以由f[i-1][(j-a*(n-i))%n]和f[i-1][(j+b*(n-i))%n]转移过来
(a和b后面都需要±(n-i)次,所以要乘上(n-i)

故f[i][j] = f[i-1][(j-a*(n-i))%n]+f[i-1][(j+b*(n-i))%n]
(注意:c++%运算有可能出现负数结果,需要自定义非负的取余运算)

4.确定初值和答案
因为未进行任何操作时数列只有0项,和为0,初始时只有这样一个合法状态,故f[0][0] = 1;
目标状态:合法转移到s,共有n-1步,故最终答案为f[n-1][s%n]

时间复杂度:O(n2)

C++代码实现

#include <iostream>

using namespace std;

const int N = 1010, base = 1e8+7;
int n, s, a, b;
int f[N][N];
//非负取模运算,%n
int mod(int x) {
    return (x%n+n)%n;
}

int main() {
    cin>>n>>s>>a>>b;
    f[0][0] = 1;
    for(int i = 1; i < n; ++i) {//枚举i-1项
        for(int j = 0; j < n; ++j) {//枚举第i项的n种可能的状态
            f[i][j] = (f[i-1][mod(j-a*(n-i))]+f[i-1][mod(j+b*(n-i))])%base;
        }
    }
    cout<<f[n-1][mod(s)];
    return 0;
}

猜你喜欢

转载自blog.csdn.net/mwl000000/article/details/108187949