1214. 波动数列——第五届蓝桥杯省赛C++A组(动态规划难题)

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。

题解
此题应该是一个组合问题
设这个数列第一项为x,设 d ∈{ +a, -b } ,则长度为n的序列所有的项为
x , x + d 1 , x + d 2 , x + d 3 , . . . , x + d n − 1 x , x + d_1, x + d_2, x + d_3, ... , x + d_{n-1} x,x+d1,x+d2,x+d3,...,x+dn1
他的和为 n x + ( n − 1 ) d 1 + ( n − 2 ) d 2 + ( n − 3 ) d 3 + . . . + d n − 1 = s nx + (n-1)d_1 + (n-2)d_2 + (n-3)d_3 + ... + d_n-1 = s nx+(n1)d1+(n2)d2+(n3)d3+...+dn1=s
因为x的大小我们无法确定,所以我们把公式变形如下
x = ( s − ( ( n − 1 ) d 1 + ( n − 2 ) d 2 + ( n − 3 ) d 3 + . . . + d n − 1 ) ) n x =\frac{( s-( (n-1)d_1+ (n-2)d_2 + (n-3)d_3 + ... + d_{n-1}) )}{n} x=n(s((n1)d1+(n2)d2+(n3)d3+...+dn1))
求满足这个式子 的方案数有多少,因为x一定是整数,所以 ( s − ( ( n − 1 ) d 1 + ( n − 2 ) d 2 + ( n − 3 ) d 3 + . . . + d n − 1 ) ) ( s-( (n-1)d_1+ (n-2)d_2 + (n-3)d_3 + ... + d_{n-1}) ) (s((n1)d1+(n2)d2+(n3)d3+...+dn1)) % n一定为 0 ,因此推出s % n == ( n − 1 ) d 1 + ( n − 2 ) d 2 + ( n − 3 ) d 3 + . . . + d n − 1 (n-1)d_1+ (n-2)d_2 + (n-3)d_3 + ... + d_{n-1} (n1)d1+(n2)d2+(n3)d3+...+dn1的和 % n,也就是两者的模n的余数必须相同
s是确定的,
所以我们最后就是要求 ( n − 1 ) d 1 + ( n − 2 ) d 2 + ( n − 3 ) d 3 + . . . + d n − 1 (n-1)d_1+ (n-2)d_2 + (n-3)d_3 + ... + d_{n-1} (n1)d1+(n2)d2+(n3)d3+...+dn1这个式子的所有可能的和模n的余数是s%n的结果数
——————————————————分割线————————————————————————————
是一个dp组合问题:
状态表示:设 f [ i ] [ j ] f[i][j] f[i][j]为选了i个数,前 i 个 d 的和模 n 的余数为 j 的集合的数量
明确目标:最后求得是 f [ n − 1 ] [ s % n ] f[n-1][s\%n] f[n1][s%n]的值
递推关系:
第 i 次选择时可以选 +a 也可以选 -b
如果选 + a,前i个数的和为
[ ( n − 1 ) d 1   + ( n − 2 ) d 2   + . . . + ( n − i − 1 ) d i + 1 + ( n − i ) a ] % n ≡ j % n [(n-1)d_1~ + (n-2)d_2~ +... +(n-i-1)d_{i+1}+(n-i)a]\%n≡j\%n [(n1)d1 +(n2)d2 +...+(ni1)di+1+(ni)a]%nj%n
( n − 1 ) d 1   + ( n − 2 ) d 2   + . . . + ( n − i − 1 ) d i + 1 ≡ j − ( n − i ) a (n-1)d_1~ + (n-2)d_2~ +... +(n-i-1)d_{i+1}≡j-(n-i)a (n1)d1 +(n2)d2 +...+(ni1)di+1j(ni)a
因为f[i][j]代表的是组合数量,因为j-(n-i)a是已经确定的数值,所以变化的数量在前面的和里面,可以推出
所以 f [ i ] [ j ] = f [ i − 1 ] [ j − a ( n − i ) ] f[i][j] = f[i-1][j-a(n-i)] f[i][j]=f[i1][ja(ni)]
同理,如果选-b, f [ i ] [ j ] = f [ i − 1 ] [ j + b ( n − i ) ] f[i][j] = f[i-1][j+b(n-i)] f[i][j]=f[i1][j+b(ni)]
f [ i ] [ j ] = f [ i − 1 ] [ j + b ( n − i ) ] + f [ i − 1 ] [ j − a ( n − i ) ] f[i][j] = f[i-1][j+b(n-i)]+f[i-1][j-a(n-i)] f[i][j]=f[i1][j+b(ni)]+f[i1][ja(ni)]
在这里插入图片描述

#include <iostream>
#include <cstring>
#include <algorithm>

using namespace std;
const int N = 1010,MOD = 100000007;

int f[N][N]; //设f[i][j]为前i个数的总和模n余数为j的集合数
int get_mod(int a,int b) //求a除以b的正余数
{
    return (a%b+b) % b;
}

int main()
{
    int n,s,a,b;
    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-1][get_mod(j-a*(n-i),n)] + f[i-1][get_mod(j+b*(n-i),n)] ) % MOD;
         }
    }
    cout<<f[n-1][get_mod(s,n)]<<endl;
    return 0;
}
#include <iostream>
#include <cstring>
#include <algorithm>

using namespace std;
const int N = 1010,MOD = 100000007;

int f[N][N]; //设f[i][j]为前i个数的总和模n余数为j的集合数
int get_mod(int a,int b) //求a除以b的正余数
{
    return (a%b+b) % b;
}

int main()
{
    int n,s,a,b;
    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-1][get_mod(j-a*(n-i),n)] + f[i-1][get_mod(j+b*(n-i),n)] ) % MOD;
         }
    }
    cout<<f[n-1][get_mod(s,n)]<<endl;
    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

葛济维的博客

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值