试题 历届试题 波动数列
链接:https://www.acwing.com/problem/content/description/1216/
原题解:https://www.acwing.com/solution/content/7507/
用到了数论和动态规划的知识。假定第一个数为x,第二个数为x + p1(pi为+a或者-b),第三个数为x + p1 + p2, ......,由于和为s,则n * x + (n - 1) * p1 + (n - 2) * p2 + ...... + p(n-1) == s,
则此时(重点来了):
s ≡ (n - 1) * p1 + (n - 2) * p2 + ...... + p(n-1) (mod n)。
对于mod n,最多1000种可能,我们设立d数组d[i][j]的i代表(n - 1) * p1 + (n - 2) * p2 + ...... + p(n-1)数列从后往前选取了i个p的时候余数为j的情况下拥有的可能数量。
由此我们得到递推公式:
d[i][j] = d[i - 1][j - a * i] + d[i - 1][j + b * i];
(pi的系数,(最后一个p(n-1)的系数是1,第一个的系数是(n-1),每次决定p是+a还是-b时都要乘以系数。)
由于s与数列对n同模,则d[n -1][s % n]代表选取了从后往前的n-1项(也就是全选完时),同时数列余数与s关于n同模的结果。
此时的个数就是最终要的答案,因为数列的值加上x个n最后的和就是s,所以全选完时,数列的和的余数为s%n的情况下加上(x+y)个n(因为数列的和也对n求过模了,我们假设和为d[n-1][s%n] + y * n)就是s了,而其他余数均不可能满足题目要求。
还要注意,算出来的j-a * i或是j + b * i 或是给的s都有可能为负数,得到一个正数的下标才能得到正确的答案,所以我们采用return (x % n + n) % n得到x(不管正负)关于n的模。
ac代码:
#include <bits/stdc++.h> using namespace std; int d[1005][1005]; const int MOD = 100000007; int n, s, a, b; int cal(int x) { return ((x % n) + n) % n;//(x + n)不一定大于n,所以不能用(x + n) % n来得到余数 } int main() { scanf("%d %d %d %d", &n, &s, &a, &b); d[0][0] = 1; for (int i = 1; i < n; ++i) { for (int j = 0; j < n; ++j) { d[i][j] = (d[i - 1][cal(j - a * i)] + d[i - 1][cal(j + b * i)]) % MOD; } } printf("%d\n", d[n - 1][cal(s)]);//s可能为负数 return 0; }