第五届蓝桥杯A组 波动数列

本质是组合问题

问题描述
  观察这个数列:
  1 3 0 2 -1 1 -2 …

这个数列中后一项总是比前一项增加2或者减少3。

栋栋对这种数列很好奇,他想知道长度为 n 和为 s 而且后一项总是比前一项增加a或者减少b的整数数列可能有多少种呢?
输入格式
  输入的第一行包含四个整数 n s a b,含义如前面说述。
输出格式
  输出一行,包含一个整数,表示满足条件的方案数。由于这个数很大,请输出方案数除以100000007的余数。
样例输入
4 10 2 3
样例输出
2
样例说明
  这两个数列分别是2 4 1 3和7 4 1 -2。
数据规模和约定
  对于10%的数据,1<=n<=5,0<=s<=5,1<=a,b<=5;
  对于30%的数据,1<=n<=30,0<=s<=30,1<=a,b<=30;
  对于50%的数据,1<=n<=50,0<=s<=50,1<=a,b<=50;
  对于70%的数据,1<=n<=100,0<=s<=500,1<=a, b<=50;
  对于100%的数据,1<=n<=1000,-1,000,000,000<=s<=1,000,000,000,1<=a, b<=1,000,000。

解析

s = x + x+d1 + x+d1+d2 + ... + x+d1+..dn-1
=> s = nx + (n-1)d1 + (n-2)d2 + ... + dn-1
=> x = s-[(n-1)d1+(n-2)d2+..dn-1] / n
由此可见 任何一组d1,d2..的取值必定对应一种原序列
一一映射=> 原序列的所有不同方案数 == 所有d1..dn-1的合法取值的方案数
因为x属于整数序列 s-[(n-1)d1+(n-2)d2+..dn-1] 必须是n的倍数
得到性质: s%n == [(n-1)d1..dn-1]%n  ,其中s是序列长度
所以这就变成了组合问题

根据y式dp:

状态表示:1,定集合 2,找属性
状态计算:1,根据最后一步划分集合为若干小集合2,计算每一子集合的状态方程

定集合:dp[i,j]表示所有只考虑前i项,且当前总和s除以n的余数是j的方案数的集合
属性:Count(方案数)
划分集合:dp[i,j]=(最后一项是+a) + (最后一项是-b)
逐一计算状态方程:
(n-1)d1+(n-2)d2+…1dn-1 => 1d1+2d2+…(n-1)dn-1
那么用表示为: d1,d2,…,di(di=+a) ,其中i属于1~n-1
如果用i-1表示i,j则 i-1,(c+i
a)%n ,其中c=d1+…+di-1
因 (c+i
a)%n=j => c=(j-ia)%n
恰好c就是j-1 故i-1,j-1: i-1,(j-i
a)%n
根据集合对称,最后一项是-b同理=> i-1,j-1: i-1,(j-i*(-b))%n
最终得到状态方程: dp[i][j]=dp[i-1,(j-ia)%n]+dp[i-1][(j+ib)%n]

细节

1.初始化dp[0][0]=1;
2.-2%10在数学上的定义是等于8 而在c++中等于-2
  所以要做一下处理:  (-2%10+10)%10==8

代码如下

import java.util.Scanner;

public class Main { 

	static final int MOD=100000007,N=1010;
	static int n,s,a,b,dp[][]=new int[N][N];
	static int get_mod(int a,int b) {
		return (a%b+b)%b;   //a%b可能是负数 加上b必定是正数 再模一个b得到数学意义的模
	}
	public static void main(String[] args) {
		Scanner in=new Scanner(System.in);
		n=in.nextInt();
		s=in.nextInt();
		a=in.nextInt();
		b=in.nextInt();
		dp[0][0]=1;
		for (int i = 1; i < n; i++) {  //i属于1~n-1
			for (int j = 0; j < n; j++) {  //j是余数在数学上属于0~n
				dp[i][j]=(dp[i-1][get_mod(j-i*a, n)]+dp[i-1][get_mod(j+i*b, n)])%MOD;
			}
		}
		System.out.println(dp[n-1][get_mod(s, n)]);
	}
}

你可能感兴趣的:(算法)