有点绕的dp问题
波动数列
https://www.acwing.com/problem/content/1216/
问题分析:
先明确题目所求:求满足条件的总方案数
需要满足的条件是:总长度为n,总和为s,后一项总是比前一项增加a或者减少b。
综合的来看,求的是方案数,又是很明显的数列运算,外加上很大的数据范围,dp无疑
1≤n≤1000,
−10^9 ≤s≤10^9,
1≤a,b≤10^6
既然是dp,就得走流程。
在这之前,看到数列,总得分析一下这个数列:
每一项的规律
a[1] = a[1], a[2] = a[1] + d1, a[3] = a[1] + d1 + d2, ...
其中di代表第i项的选择,即+a或者-b
通项和
s = a[1] + a[2] + ... + a[n]
相对来说,每一项都有a[1], 并且a[1]是不确定的,想要遍历所有的a[1] 是不现实的,况且每一个a[1]对应的数列是不唯一的,但每一种数列都会唯一的产生一个a[1],很容易想到提取a[1],即
a[1] = {s - {0 + (a[2] - a[1]) + ... + (a[n] - a[1])}}
n * a[1] = s - {d2 + (d2 + d3) + ... + (d2 + d3 + ... + dn)}
进一步看,
a[1] * n = s - [(n - 1)*d2 + (n - 2)d3 + ... + dn]
显然,右边的两项需要模n同余,所以,我们成功的把一个混乱的通项和变成了一个有规律的,可以逐步递加的一个式子,并且不必考虑a[1]是啥。我看网上有一部分同学都卡在了同一个位置-----就是不能很好的将a[i]的每一项分离开,进行降维打击。只要理解了上面式子的含义,这个问题就成功的解决了。总结的说我们接下来要做的就是在每一次循环中解决一个d,比如说第一次循环就把(n - 1)d2记录下来,而不是只记录一个d2.
(这里有点绕,多琢磨一下就出来了,这也是一部分题解没有说明白的地方——到底是对{a[n]}运算还是对(n - 1)*d2 + (n - 2)d3 + ... + dn进行运算)
接下来走个流程,把问题重新用dp的方法过一遍
第一步 :状态分析
-
f(i)表示要选完第i个的方案数,但这是有意义不明的,到底是否满足同余关系,所以需要再来一维
-
f(i,j)表示s[i] mod n = j,即
(n - 1)*d2 + (n - 2)d3 + ... + (n - i + 1)di
模n余数为j的所有方案数
第二步: 状态计算
走到了第i个位置,现在面临着加a还是减b的问题,现在的状态是f(i, j)
(1)加a
现在已经有第i - 1位置的情况:
(n - 1)*d2 + (n - 2)d3 + ... + (n - i + 2)d(i-1)
所以需要满足
((n - 1)*d2 + (n - 2)d3 + ... + (n - i + 2) * d(n-1) + (n - i + 1) * a ) == j\ \ mod \ \ (n)
可以看到在di位置的系数是(n - i + 1),那么也就是说如果第i步要加(n - i + 1)个a,那么就能得出i-1位置的状态
((n - 1)*d2 + (n - 2)d3 + ... + (n - i + 2) * d(n-1) ) == {j - (n - i + 1) * a}\ \ mod \ \ (n)
用集合的方式表示就是
f(i, j) = f(i - 1, (j - (n - i + 1) * a) % n)
(2)减b,通过上述分析有
((n - 1)*d2 + (n - 2)d3 + ... + (n - i + 2) * d(n-1) ) == {j + (n - i + 1) * b}\ \ mod \ \ (n)
f(i, j) = f(i - 1, (j + (n - i + 1)*b) % n)
综上,
f(i, j) = f(i - 1, (j - a) % x) + f(i - 1, (j + b) % x)
最终的结果就是f(n)
到这里,这个题的核心就被搞清楚了,接下来就是代码部分了。
上代码:
#include<iostream>
using namespace std;
const int N = 1010;
const int M = 100000007;
int f[N][N];
int mod(int a, int b)
{
return (a % b + b)% b;
}
int main()
{
int n, s, a, b;
cin >> n >> s >> a >> b;
f[0][0] = 1;//边界问题,如果一个数都没有的话,余数肯定是0,这是一个方案
for(int i = 1; i < n; i ++)//由于不需要考虑首项,所以循环n - 1次就可以了
for(int j = 0; j < n; j ++)
f[i][j] = (f[i - 1][mod((j - (n - i)* a), n)] + f[i - 1][mod((j + (n - i)* b), n)])% M;
cout << f[n - 1][mod(s,n)];
return 0;
}
注意:
1.边界问题,在刚开始的时候,一个数都没计算,即处于a[1]状态,这时候的方案有且只有一个,所以需要给f(0, 0)赋值为1
2.这里有一个需要注意的地方,在c++里,负数取余会得到负数
cout << -3 % 5;
cout << -3 % -5;
这里得出的结果都是-3,所以mod函数绝对不是脱了裤子放屁,但是在python里就不需要这么做了。

浙公网安备 33010602011771号