【写在前面】
第一次写CSDN~谢谢各位点进来,如有意见建议欢迎提出~!*★,°*:.☆( ̄▽ ̄)/$:*.°★* 。
【原题呈现】
【问题描述】
帕姆听说贝洛伯格的历史文化博物馆重新开放了,非常好奇博物馆经营情况。
开拓者的经营水平飘忽不定,从第二天开始,后一天的经营额总是比前一天多 a 冬城盾或者少 b 冬城盾,第一天的营业额可以是任意整数。
开拓者经营了 n 天就被辞退了,总的经营额为 s。营业额可以为负。
帕姆想知道总共有多少种满足上述条件的可能的经营情况,你能帮帮它吗?
【输入形式】
输入的第一行包含四个整数 n、s、a、b,含义如前所述。
对于70%的数据,1≤n≤100,0≤s≤500,1≤a、b≤50;
对于100%的数据,1≤n≤1000,-10^9≤s≤10^9,1≤a、b≤10^6
【输出形式】
输出一行,包含一个整数,表示满足条件的方案数。
由于这个数可能很大,请输出方案数除以100000007的余数。
【样例输入】
4 10 2 3
【样例输出】
2
【样例说明】
这两种经营可能情况分别是 2 4 1 3 和 7 4 1 -2。
【思路分析】
【初见】
第一眼看,这不应该dp嘛。定义 为在前
i
天,总经营额为 j
的方案数。
对于每一天,我们有两种选择:增加 a
冬城盾或减少 b
冬城盾。因此,状态转移方程为:
但是,我们很快发现,题目中的”初始经营额“是不固定的。这也就导致我们转移方程的原始状态是不确定的,这条思路很快就走不通了。
【再看】
题目虽然没有给出初始营业额,但告诉了我们最终营业额和每一天的营业额度,这在某种程度上好像在暗示我们将初始营业额表示出来。由于初始营业额一定是整数,我们似乎可以借助这条性质来判断其是否满足条件。
不管怎么说,Give a try!
列出以下方程:
其中,分别为初始营业额,最终营业额;n为天数;
为第i天的营业额。
化简等式,即有
由该式子我们可以得出:
- 当x全部取a,对于给定的S,初始营业额最小;x取-b时初始营业额最大
- 当我们将第i天的营业额从增加a替换为减少b时,对于初始营业额影响应为
因此,我们只需要将初始状态设置成为初始营业额的最小值(即每天营业额都是增加a),然后枚举出所有天数组合的可能性方案,并判断当前获得的初始营业额是否为整数,此时问题就迎刃而解了!
【可能性计算】
由于天数最多有n-1天,而组合上限是,对于每一个组合值,我们都需要找到从0~n-1中组合为该值的所有方案数,且要求不能重复选取。熟悉的味道来了——dp!
定义 为在前
i
天,组合总天数为 j
的方案数,得到状态转移方程:
将其优化成一维dp,注意j的反向:
for (int i = 1; i <= n - 1; ++i) {
for (int j = max_sum; j >= i; --j) {
dp[j] += dp[j - i];
}
}
这样我们就得到了对于每个总天数t,在0~n-1天中不重复地选取组成t的所有方案数dp[t]。
【整数判定】
看到这里,先感谢您对本文章的支持~*★,°*:.☆( ̄▽ ̄)/$:*.°★* 。
这里我们先假设每天的营业额都比昨天多a,这样在最终营业额一定的情况下,此时的初始营业额一定是最少的,设为。同时总天数设为
,
由公式知,若使计算结果为整数,等式右侧分子部分应整除n。不妨设:
同时对于每个天数的增加量a+b,有:
那么就有:
即:
当从0增大到
,只要满足这两项对n的余数为0,就能说明得出的初始营业额为整数,符合我们的要求,进而将此时的方案数统计下来,累加之,就是最终的答案。
代码实现如下:
int low = -sumn * a + s;
int cnt = 0;
int f = low % n;
int q = a + b % n;
for (int i = 0; i <= max_sum; i++) {
if ((i * q + f)% n == 0) {
cnt += dp[i];
cnt %= N;
}
}
return cnt;
【优化】
遗憾的是,目前代码只能通过4个样例,这说明我们的基本思路大致正确,但在某些方面仍然存在不足。🤔ing——
由于这道题数据量很大,我们可能对数据的处理还不够细致。考虑到cnt实质上是所有符合要求的dp结果之和,dp结果也有可能达到很大,同样需要取模运算。
而对于的s,
的n和
的a,b;在我们的算法中有很大可能超出int表示的范围,选择把范围开到long long以保万无一失。
修改过后的代码如下:
ll low = -max_sum * (ll)a + s;
// 使用 long long 类型进行计算
int cnt = 0;
ll f = low % n;
ll q = (a + b) % n;
for (int i = 0; i <= max_sum; i++) {
if (((ll)i * q + f) % (ll) n == 0) {
cnt += dp[i];
cnt = cnt % N;
}
}
提交,全部AC~
【完整代码】
完整代码如下:
//Originated by Emanuel_CH in CSDN
#include<iostream>
#include<string>
#include<cmath>
using namespace std;
typedef long long ll; // 使用 long long 类型以支持更大的数
const int N = 100000007;
ll dp[1000010];
int main() {
int n, a, b;
ll s; // 使用 long long 类型存储 s
cin >> n >> s >> a >> b;
int max_sum = n * (n - 1) / 2;
dp[0] = 1;//初始状态为1
for (int i = 1; i <= n - 1; ++i) {
for (int j = max_sum; j >= i; --j) {
dp[j] += dp[j - i];
dp[j] %= N;
}
}
ll low = -max_sum * (ll)a + s;
// 使用 long long 类型进行计算
int cnt = 0;
ll f = low % n;
ll q = (a + b) % n;
for (int i = 0; i <= max_sum; i++) {
if (((ll)i * q + f) % (ll) n == 0) {
cnt += dp[i];
cnt = cnt % N;
}
}
cout << cnt;
return 0;
}
【时间复杂度】
-
第一个双层循环:外层循环从1到n-1,内层循环从max_sum到i,其中max_sum为n*(n-1)/2。因此,这部分的时间复杂度为O(n^2)。
-
第二个循环:从0到max_sum,时间复杂度为O(n^2),因为max_sum的最大值是n*(n-1)/2。
整个程序的时间复杂度主要由两个O(n^2)的循环决定,因此总的时间复杂度为O(n^2)。