题目链接:
PREV-30 波动数列
思路:
设这个数列首项为
A
A
A,由题意可知总和一定可以表示成
n
A
+
x
a
−
y
b
(
x
+
y
=
n
(
n
−
1
)
2
)
nA+xa-yb(x+y=\frac{n(n-1)}{2})
nA+xa−yb(x+y=2n(n−1))的形式,其中
x
x
x为
+
a
+a
+a操作的次数和,
y
y
y为
−
b
-b
−b操作的次数和;
那么我们有
n
A
=
s
−
x
a
+
y
b
(
x
+
y
=
n
(
n
−
1
)
2
&
0
≤
x
,
y
≤
n
(
n
−
1
)
2
)
nA=s-xa+yb\quad(x+y=\frac{n(n-1)}{2}\&0\leq x,y\leq\frac{n(n-1)}{2})
nA=s−xa+yb(x+y=2n(n−1)&0≤x,y≤2n(n−1));
而我们知道当且仅当右式可以被
n
n
n整除时
A
A
A有解,因此我们需要枚举每一组
(
x
,
y
)
(x,y)
(x,y),带进右式检查此时
A
A
A是否有解,如果有解,那么我们的答案应该加上
x
,
y
x,y
x,y为当前值时、数列的
+
a
,
−
b
+a,-b
+a,−b分布情况的种数;
|------------------------------------------------------------------------------------------------------------|
此时的问题只剩下:对于给定的
(
n
,
x
)
(n,x)
(n,x),数列的可能种数(
y
y
y可以通过
n
(
n
−
1
)
2
−
x
\frac{n(n-1)}{2}-x
2n(n−1)−x计算得出);
对于每一项,它只有
+
a
+a
+a或者不
+
a
+a
+a,因此是0/1背包的模型,我们设
d
p
[
i
]
[
j
]
(
0
≤
i
<
n
)
dp[i][j](0\leq i<n)
dp[i][j](0≤i<n)为:长度为
n
n
n的数列,第
0
0
0项到第
i
i
i项的
+
a
+a
+a情况对最后
+
a
+a
+a总和的影响数;
我们知道第
i
i
i项如果
+
a
+a
+a,那么后面的所有项都会受到一次
+
a
+a
+a的影响,所以第
i
i
i如果是
+
a
+a
+a操作,那么对整体的贡献就是
n
−
i
n-i
n−i个
+
a
+a
+a(注意
i
i
i从
0
0
0开始取),因此
d
p
[
i
]
[
j
]
+
=
d
p
[
i
−
1
]
[
j
−
(
n
−
i
)
]
dp[i][j]+=dp[i-1][j-(n-i)]
dp[i][j]+=dp[i−1][j−(n−i)];如果第
i
i
i项不执行
+
a
+a
+a,那么它贡献为
j
j
j时的种数就和前一项贡献为
j
j
j的种数是一样的;
综上:
d
p
[
i
]
[
j
]
=
d
p
[
i
−
1
]
[
j
]
+
d
p
[
i
−
1
]
[
j
−
n
+
i
]
dp[i][j] = dp[i-1][j] + dp[i-1][j - n + i]
dp[i][j]=dp[i−1][j]+dp[i−1][j−n+i]
而这种二维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;
}