题目
输入样例:
4 10 2 3
输出样例:
2
样例解释
两个满足条件的数列分别是2 4 1 3和7 4 1 -2。
思路
上来先理解题意,本题求的是“长度为n 总和为s的……数列的数目”。
假设第一项为x,增加 a 或者减少 b用di表示,那么满足题目要求(后一项总是比前一项增加 a或者减少 b)的数列都可以表示成:
(1)
数列前n项的和 s可以表示为:
那么:
(2)
从上面(2)式可以看出,对于确定的s,n,每个x都由一组(d1, d2, ..., dn)唯一确定,同时,如果确定了一个x,那么一组(d1, d2, ..., dn)也会唯一确定,即一个数列就确定了,因此(d1, d2, ..., dn)的合法组数等于满足题目要求的数列总数。
那什么样的一组(d1, d2, ..., dn)是合法解?由于x是整数,因此(2)式中的分子必须是n的倍数,即s和模n要同余。
状态表示
由上分析得知本题涉及到的变量为(d1, d2, ..., dn),以及 mod n的同余数j。那么只要算出所有的组合模n的余数,将不同余数用一个数组f统计起来,比如余数为1的组合有多少种。那么最后输出f[s%n]即可。
- 集合定义f[i, j]:对于(d1,d2,…di,...dn),只考虑前i项,当前的总和(注意是的总和)除以n的余数为j的组合 的集合(数目)。
- 属性:前i项,当前总和除以n的余数为j的组合 的集合(数目)。
状态计算
状态计算一般就是抓住最后一步的不同来划分集合。本题最后一步的di可能为 加a 或者 减b
(1≤a,b≤10^6),划分的集合可看下图。
假设组合(d1,d2,…di)的第i项di为a,
那怎么由前面的状态推出f[i, j]呢?我们知道不管前i - 1项中的d是怎么变化的,只要是第i项di为a,并且 的(d1,d2,…di-1)组合都是合法的。那么这些(d1,d2,…di-1)组合的个数又怎么求呢??
我们两边都减去(n - i)a,得:
发现左式和 右式同余,根据集合定义,f[i - 1][([ j - (n - i)*a) mod n] 就是这些(d1,d2,…di-1)组合的个数。
而对于第i项di为-b,同理可得f[i - 1][([ j + (n - i)*a) mod n] 就是这些(d1,d2,…di-1)组合的个数。
细节
- 下标不能为负,j - (n - i)*a 可能为负数,而c++中对一个负数取模,相当于先用负数的绝对值取模,然后再加负号。 因此我们需要把j - (n - i)*a mod n转为正数。我们选择模 n 同余类中的最小非负数作为该同余类的代表数,所有负数x 模 n 都可以通过这条公式转化为代表数:(x % n + n) % n。
- 长度n = 1时,根据(2)式,x只能是0。那么f[0][0] = 1(别忘了集合定义!!)
代码
/*一道题,一包烟,一坐就是一整天*/
#include<bits/stdc++.h>
using namespace std;
const int MOD = 100000007, N = 1010;
int f[N][N];
int n, s, a, b;//求x mod n的正余数
int get_mod(int x)
{return (x % n + n) % n;
}int main()
{ios::sync_with_stdio(false), cin.tie(0), cout.tie(0);cin >> n >> s >> a >> b;//边界处理f[0][0] = 1;for (int i = 1; i < n; i ++){for (int j = 0; j < n; j ++){f[i][j] = (f[i][j] + f[i - 1][get_mod(j - (n - i)*a)]) % MOD;f[i][j] = (f[i][j] + f[i - 1][get_mod(j + (n - i)*b)]) % MOD;}}//s可能为负数!!cout << f[n - 1][get_mod(s)];}
资料
- 模运算定理
- 同余类