1214 波动数列

题意:要构造一段数列,每一个元素(第一个元素外,第一个元素可以是任意数)总是前一项元素的+a或者-b,我们要找出所有数列中每个元素和为s的方案

题目分析:S=nx+(n-1)d1+(n-2)d2+(n-3)d3+(n-4)d4...+dn-1

           其中,x是首元素,d1,d2...都是公差,是a或者-b

  我们可以得到

                x=(S-((n-1)d1+(n-2)d2+(n-3)d3...dn-1))/n 

x一定是整数,所以(S-((n-1)d1+(n-2)d2+(n-3)d3...dn-1))一定是n的倍数

由于

S-(n-1)d1+(n-2)d2+...+dn-1 /n=x ,x 为正整数,则说明分子一定是 nn 的倍数,简单可证 S%n与 ((n−1)d1+(n−2)d2+…+dn−1)%n 余数相同时

证明:(S-((n−1)d1+(n−2)d2+…+dn−1)%n==0,则S%n==((n−1)d1+(n−2)d2+…+dn−1)%n)

分子为 n 的倍数。则最终f[n][s%n]的方案数,就是 (n−1)d1+(n−2)d2+…+dn−1 这 n−1 个 d 的合法方案数,两者是等价的。

所以

我们把求构造s的方案数转换为了 求n-1个d的方案数---》于是就变成了组合问题,由于第一个数是随机的,范围很大,只要一个方案中的n-1个d这一串序列是唯一的,那么首元素x也是唯一的,不需要去枚举。

我们要枚举的每一个d,并找出最后合法的方案

由于数据范围n<=1000,枚举每一个d是2的1000次方,不合理,所以采用动态规划的方法

 首元素的前一项和为0,所以dp[2][j]  j取值是0~n-1,这里用j(前i-1项和模n)表示每一项取了a还是b

由于j表示的是(S-(n-1)d1...di-1)%n,则第二维的下标等价于(S-(n-1)d1...(n-i+1)di-1-(n-i)di)%n的值

一些问题

1.为什么每一项都是模n

S=nx+(n-1)d1+(n-2)d2+(n-3)d3+(n-4)d4...+dn-1

 可知,我们要确定一个方案,就是让他的d序列是唯一的,s%n==((n-1)d1+(n-2)d2...)%n

我们每一项都在求d,所以是模n

2.为什么代码中是j-(n-i)*a

i表示第i项,从第一项开始求(n-i)a==求(n-1)d...以此类推

3.为什么初始化时dp[0][0]=1

我们只要求d的序列,所以dp[0][0]=1是一个根节点,确保一个根节点开始延展成多个树(每一个d都可以看为一个树结点和他们附带的子树)

4.为什么循环i<n,并且输出的是n-1

数组的长度是n,但是我们枚举的d的序列,而没有枚举首元素x,所以在我们的集合中数组的长度是n-1

以下附上代码

#include <iostream>
#include <algorithm>
const int P = 100000007;
using namespace std;
int dp[1002][1002];
int get_mod(int a, int b)
{	//确保唯一正余数
	//因为a%b可能是负的,而这个a模b一定是正
	return (a % b + b) % b;
}
int main()
{
	int n, s, a, b;
	//n表示数列长度
	//s表示数列和
	//a表示递增公差,b表示递减公差
	cin >> n >> s >> a >> b;
	dp[0][0] = 1;							//保证序列唯一
	for (int i = 1;i < n;i++)
		for (int j = 0;j < n;j++)           //j是前面所有项和/n的余数,所以取值是0~n-1
		{   //在i==1时候枚举j就等于在枚举首字母x
			dp[i][j] = dp[i - 1][get_mod(j - (n - i) * a, n)]%P + dp[i - 1][get_mod(j + (n - i) * b, n)]%P;
		}
	cout << dp[n-1][get_mod(s, n)]%P;

}

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值