试题 历届试题 波动数列

链接: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;
}

 

posted @ 2020-09-09 10:03  funforever  阅读(192)  评论(0编辑  收藏  举报