波动数列-动态规划(蓝桥真题)

题目链接https://www.acwing.com/problem/content/1216/

思路来源https://www.acwing.com/solution/content/72795/

 题目详情:

观察这个数列:

1 3 0 2 -1 1 -2 …

这个数列中后一项总是比前一项增加2或者减少3,且每一项都为整数

栋栋对这种数列很好奇,他想知道长度为 n 和为 s 而且后一项总是比前一项增加 a 或者减少 b 的整数数列可能有多少种呢?

输入格式

共一行,包含四个整数 n,s,a,b,含义如前面所述。

输出格式

共一行,包含一个整数,表示满足条件的方案数。

由于这个数很大,请输出方案数除以 100000007 的余数。

数据范围

1≤n≤1000
−10^9≤s≤10^9
1≤a,b≤10^6

输入样例:

4 10 2 3

输出样例:

2

样例解释

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

分析: 

  • 转化问题:

设这个数的第一项为x,设d∈{+a,-b},则长度为n的序列所有的数为:                                                                        

                                          x,x+d1,x+d2,x+d3,…,x+dn−1

可以化为:

 nx + (n-1)*d1 + (n-2)*d2 + ... + dn-1 = s

x = (s - ((n-1)*d1 + (n-2)*d2 + ... + dn-1)) / n

求满足这个式子 的方案数有多少,因为x一定是整数,所以 (s−((n−1)d1+(n−2)d2+(n−3)d3+…+dn−1)) % n 一定为 0 ,因此推出 s % n == (n−1)d1+(n−2)d2+(n−3)d3+…+dn−1(n−1)d1+(n−2)d2+(n−3)d3+…+dn−1的和 % n,也就是两者的模n的余数必须相同。
而s是确定的,所以我们最后就是要求 (n−1)d1+(n−2)d2+(n−3)d3+…+dn−1 这个式子的所有可能的和模n的余数是  s%n  的结果数。

  • 解决问题:

在上一个步骤中,我们将问题转换成了 dp组合问题,接下来用dp的方法来解决:

我们按第 i 个数+a 或者是 -b 来划分集合,前 i 个数的和 % n的结果即 j 的值会因为我们第i步选择的不同(+a或-b)而不同,但范围始终在 0~n-1

状态表示:设 f[i][j] 为选了i个数,前 i 个 d 的和模 n 的余数为 j 的集合的数量
明确目标:最后求得是 f[n−1][s%n] 的值

递推关系:第 i 次可以选择 +a 也可以选 -b

如果选 +a ,前 i 个数的和为 :

        [(n−1)d1 +(n−2)d2 +…+(n−(i−1))di−1+(n−i)a]%n ≡ j%n     (j=s%n,所以j%n = j)

        (n−1)d1 +(n−2)d2 +…+(n−(i−1))di−1 ≡ j−(n−i)a  (mod n)(n的同余关系)

因为 f[i][j ]代表的是组合数量,因为 j-(n-i)a 是已经确定的数值,所以变化的数量在前面的和里面,可以推出 f[i][j]=f[i−1][(j−a(n−i))%n]

同理,如果选-b,

        f[i][j]=f[i−1][j+b(n−i)]

状态转移方程为:f[i][j]=f[i−1][(j+b(n−i))%n]+f[i−1][(j−a(n−i))%n]

注意:

图中的 i*a,它与(n - 1)* a 的结果是等价的,它相当于将 (n−1)d1+(n−2)d2+(n−3)d3+…+dn−1 变换成 1*d1+2*d2+...+(n-1)*dn ,不是等价变换,而是将各个变量的含义交换一下,因为题中+a,-b这两个选择可以任意组合,没有限制

 

代码:

//2022.5.25 
#include<iostream>
#include<algorithm>
using namespace std;

const int N=1010,MOD = 100000007;
int n,s,a,b;
int f[N][N];

int getMod(int x,int y)
{
	//在计算机中 负数模正数得到的不是我们现实生活中计算出的正余数
	//如在我们平时的计算中,-2%10=8,但在计算机中结果为-2
	//所以要进行转换,转换过程如下 
	return (x%y + y)%y;
}

int main()
{
	cin>>n>>s>>a>>b;
	
	//前 0 项的和 %n 的结果为 0 的集合数,也是一种合法情况 
	//可以理解成当 n == s 时,此时的答案就为 1 
	f[0][0] = 1;
	
	//为什么 i 不会等于 n 呢?
	//因为 第一项 x 我们并没有算进来,x在此题中我们当作已知的由系统给出
	//我们计算的实际是序列中的第2项~第n-1项 
	for(int i=1;i<n;i++)
		for(int j = 0;j <= n-1; j++)
		{
			f[i][j] = (f[i-1][getMod(j-(n-i)*a,n)]+
				  	   f[i-1][getMod(j+(n-i)*b,n)])%MOD; 	
		} 
		
	cout<<f[n-1][getMod(s,n)];
		
	return 0;
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值