题目来源:洛谷P8614(https://www.luogu.com.cn/problem/P8614)
题目大意:一整数数列,长度为n,和为s,后一个数字为前一个数字 +a / -b
计数题: dp / 数学找规律
此题中 n的范围为<1000,可以通过dp( 因为O(n2)能够实现 )
公式推导:
令 P = +a 或者 -b
s=na1+(0+1+2+···+n-1)P
=na1 + Z
因为a1一定是整数,所以 s mod n 和 Z mod n 的值应该相同。
利用这一特点,dp的转换方程即可产生。
转换方程:
dp[i][j]
表示后 i 项的和模 n 后等于 j 时的方案数。
dp[i][j] = (dp[i-1][ MOD(j-i*a%n,n) ] + dp[i-1][ MOD(j+i*b%n,n) ]) % mod;
其中MOD函数为 (x % n + n) % n
要求 s % n
的值和除了a1 的其他数字之和模n的值相同
从后往前思考:
当 i = n-1 时,要求与 s % n
相同。
i从 n-2 到 n-1 的过程中,P等于 +a 或 -b ;所以总和会 +(n-1)a 或是 -(n-1)b
(这里i变化后加的数字相当于数列的第二个数字,其实 i 从 0 到 n-1 的变化过程中,代码中加的数字是从数列的最后一个数字开始往前加的 )
所以求 dp[i][j]
就相当于把 dp[i-1]
相对应的两个答案相加
注意 :
- 为何MOD函数需要 +n 再 %n : 防止x为负数,在数组取值时出现数组越界问题
- 为何最后的答案为
dp[n-1][(s%n+n)%n]
: 公式成立的条件为第一个数a1为整数,所以dp过程中不包括a1,所以P操作只需要进行 n-1 次。 dp[0][0]=1
: 即当0个数字相加时,只有和为0存在方案,方案数为1,和为其他数字的方案数都为0。
代码:
#include<bits/stdc++.h>
using namespace std;
#define ll long long
const ll mod=1e8+7;
const int N=1010;
ll MOD( ll x ,ll n){return (x%n+n)%n;
}
ll dp[N][N];
int main(){ll n,s,a,b;cin>>n>>s>>a>>b;dp[0][0]=1;for(int j=1;j<n;j++) dp[0][j]=0;for(int i=1;i<=n;i++){for(int j=0;j<n;j++){dp[i][j]=(dp[i-1][ MOD(j-i*a%n,n) ]+dp[i-1][ MOD(j+i*b%n,n) ])%mod;}}cout<<dp[n-1][(s%n+n)%n]<<endl;return 0;
}