题目链接:
PREV-30 波动数列
思路:
设这个数列首项为
,由题意可知总和一定可以表示成
的形式,其中
为
操作的次数和,
为
操作的次数和;
那么我们有
;
而我们知道当且仅当右式可以被
整除时
有解,因此我们需要枚举每一组
,带进右式检查此时
是否有解,如果有解,那么我们的答案应该加上
为当前值时、数列的
分布情况的种数;
|------------------------------------------------------------------------------------------------------------|
此时的问题只剩下:对于给定的
,数列的可能种数(
可以通过
计算得出);
对于每一项,它只有
或者不
,因此是0/1背包的模型,我们设
为:长度为
的数列,第
项到第
项的
情况对最后
总和的影响数;
我们知道第
项如果
,那么后面的所有项都会受到一次
的影响,所以第
如果是
操作,那么对整体的贡献就是
个
(注意
从
开始取),因此
;如果第
项不执行
,那么它贡献为
时的种数就和前一项贡献为
的种数是一样的;
综上:
而这种二维dp数组可以通过倒着求而变成一维滚动数组以节省大量空间;
代码:
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const ll mod = 100000007;
ll n, s, a, b;
ll dp[1000000];
int main() {
#ifdef MyTest
freopen("Sakura.txt", "r", stdin);
#endif
cin >> n >> s >> a >> b;
dp[0] = 1;
for(int i = 1; i < n; ++i)
for(int j = n * i - (1 + i) * i / 2; j >= n - i; --j) {
dp[j] = (dp[j] + dp[j - n + i]) % mod;
}
ll ans = 0, tot = n * (n - 1) >> 1;
for(ll i = 0; i <= tot; ++i)
if((s - i * a + (tot - i) * b) % n == 0) {
ans = (ans + dp[i]) % mod;
}
cout << ans;
return 0;
}