*波动数列c++

题目

输入样例:

4 10 2 3

输出样例:

2

样例解释

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

思路

上来先理解题意,本题求的是“长度为n  总和为s的……数列的数目”。

假设第一项为x,增加 a 或者减少 b用di表示,那么满足题目要求(后一项总是比前一项增加 a或者减少 b)的数列都可以表示成:

x, x + d_{1}, x + d_{1} + d_{2}, x + d_{1} + d_{2} + d_{3} ..., x + d_{1} + d_{2} ... d_{n - 1}          (1)

数列前n项的和 s可以表示为:

s = nx + (n - 1)d_{1} + (n - 2)d_{2}, (n - 3)d_{3} ...+ (n - i)d_{i} + ... + d_{n}

那么:

x =\frac{​{s - \left [(n - 1)d_{1} + (n - 2)d_{2} + (n - 3)d_{3} ...+ (n - i)d_{i} + ... + d_{n}\right ]}}{n}                 (2)

        

        从上面(2)式可以看出,对于确定的s,n,每个x都由一组(d1, d2, ..., dn)唯一确定,同时,如果确定了一个x,那么一组(d1, d2, ..., dn)也会唯一确定,即一个数列就确定了,因此(d1, d2, ..., dn)的合法组数等于满足题目要求的数列总数。

        那什么样的一组(d1, d2, ..., dn)是合法解?由于x是整数,因此(2)式中的分子必须是n的倍数,即s和(n - 1)d_{1} + (n - 2)d_{2}, ... + d_{n}模n要同余。

状态表示

由上分析得知本题涉及到的变量为(d1, d2, ..., dn),以及(n - 1)d_{1} + (n - 2)d_{2}, ... + d_{n} mod n的同余数j。那么只要算出所有(n - 1)d_{1} + (n - 2)d_{2}, ... + d_{n}的组合模n的余数,将不同余数用一个数组f统计起来,比如余数为1的组合有多少种。那么最后输出f[s%n]即可。

  • 集合定义f[i, j]:对于(d1,d2,…di,...dn),只考虑前i项,当前的总和(注意是(n - 1)d_{1} + (n - 2)d_{2}, ... + d_{i}的总和)除以n的余数为j的组合 的集合(数目)。
  • 属性:前i项,当前总和除以n的余数为j的组合 的集合(数目)。

状态计算

状态计算一般就是抓住最后一步的不同来划分集合。本题最后一步的di可能为 加a 或者 减b

(1≤a,b≤10^6),划分的集合可看下图。

        假设组合(d1,d2,…di)的第i项di为a,((n - 1)d_{1} + (n - 2)d_{2}, ... + (n - i)a) mod n \equiv j

        那怎么由前面的状态推出f[i, j]呢?我们知道不管前i - 1项中的d是怎么变化的,只要是第i项di为a,并且((n - 1)d_{1} + (n - 2)d_{2}, ... + (n - i)a) mod n \equiv j 的(d1,d2,…di-1)组合都是合法的。那么这些(d1,d2,…di-1)组合的个数又怎么求呢??

我们两边都减去(n - i)a,得:

((n - 1)d_{1} + (n - 2)d_{2}, ... (n - i + 1)d_{i - 1}) mod n \equiv (j - (n - i)a)mod n

发现左式和 右式同余,根据集合定义,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)];
  
}

资料
  • 15
    点赞
  • 13
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值