蓝桥杯 波动数列题解

蓝桥杯 波动数列题解

    • 问题描述
    • 问题分析
    • 代码(满分)

问题描述

观察这个数列: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。
【数据规模与约定】

1<=n<=1000,-1,000,000,000<=s<=1,000,000,000,1<=a, b<=1,000,000

问题分析

假设初始数列为递增等差数列,则
n a 0 + n ( n − 1 ) a / 2 − ( a + b ) k = s na_0+n(n-1)a/2-(a+b)k=s na0+n(n1)a/2(a+b)k=s
其中 a 0 a_0 a0为数列第一项, k = ∑ i = 1 n − 1 c i ∗ i , c i ∈ { 0 , 1 } k=\sum_{i=1}^{n-1}c_i*i,c_i\in\{0,1\} k=i=1n1cii,ci{0,1}.
某个 k k k对应的 c c c序列种类可以通过动态规划求解,时间复杂度为 o ( n 2 ) o(n^2) o(n2).
方程可以抽象为 a x + b y = c ax+by=c ax+by=c求解 a 0 、 k a_0、k a0k的初始值以及 n n n a + b a+b a+b的最大公约数,进而获得k的搜索间隔 d = n / g c d ( n , a + b ) d=n/gcd(n,a+b) d=n/gcd(n,a+b).
最后以 ( k % d + d ) % d (k\%d+d)\%d (k%d+d)%d为初值,d为搜索间隙在 [ 0 , n ∗ ( n − 1 ) / 2 ] [0,n*(n-1)/2] [0,n(n1)/2]内相加所有c序列种类数

代码(满分)

#include
#include
using namespace std;

typedef long long ll;
const int MAX=5e5+2,mod=100000007;
ll ch[MAX]={0};
ll n,s,a,b;
ll a0,k,gcd;
//ax+by=c
//bx'+(a%b)y'=c
//bx'+(a-a/b*b)y'=c
//ay'+b(x'-a/b*y')=c
ll gcdxy(ll n,ll m,ll &x,ll &y,ll sum){
	if(m){
		ll gcd=gcdxy(m,n%m,y,x,sum);
		y-=n/m*x;
		return gcd;
	}
	else{
		if(sum%n){
			x=y=0;
			return -1;
		}
		else{
			x=sum/n;
			y=0;
			return n;
		}
	}
}

int main(){
	scanf("%lld%lld%lld%lld",&n,&s,&a,&b);
	ch[0]=1;
	ll ind=0;
	for(int i=1;i<n;i++){
		ch[ind+i]+=ch[ind];
		for(int j=ind-1;j>=0;j--){
			if(ch[j]){
				ch[j+i]+=ch[j];
				ch[j+i]%=mod;
			}
		}
		ind=ind+i;
	}
	gcd=gcdxy(n,a+b,a0,k,s-n*(n-1)*a/2);
	if(gcd==-1){
		cout<<0<<endl;
		return 0;
	}
	//printf("gcd=%lld,a0=%lld,k=%lld\n",gcd,a0,-k);
	k=-k;
	ll d=n/gcd;
	ll maxnum=n*(n-1)/2;
	ll sumnum=0;
	ind=(k%d+d)%d;
	for(int i=ind;i<=maxnum;i+=d){
		sumnum+=ch[i];
		sumnum%=mod;
	}
	printf("%lld\n",sumnum);
	return 0;
}

你可能感兴趣的:(算法,算法,程序设计)