题目内容
小明给出了一个关于未知量 x 的多项式。这个多项式以字符串的形式表示,它由若干个形如"(x-d)"或"(x+d)"的括号表达式相乘构成。其中,d 是一个 1 到 9 之间的数字字符。
小明想知道,当这个多项式完全展开后,x 的一次项(即 x^1 项)的系数是多少? 请计算这个系数,由于答案可能很大,请将答案对10007取模后输出。
输入描述
在一行上输入一个长度为5≤len<10^5的字符串 s,表示给定的多项式。
题目保证字符串 s 严格由若干个"(x-d)"或"(x+d)"形式的子串拼接而成,其中 d 是 1 到 9 的数字字符。因此,字符串的长度一定是 5 的倍数。
输出描述
输出一个整数,表示多项式展开后 x 的一次项系数。由于答案可能很大,请将答案对10007取模后输出。
示例 1
输入
scss
(x-1)(x+5)
输出
4
说明 在这个样例中,多项式为 (x-1)(x+5)。 展开后得到 x^2 + 5x - x - 5 = x^2 + 4x - 5。 其中 x 的一次项系数是 4。对 10007 取模后结果仍为 4。
示例 2
输入
scss
(x-1)(x+2)(x+3)
输出
1
在这个样例中,多项式为 (x-1)(x+2)(x+3)= x^3 + 4x^2 + x - 6。 其中 x 的一次项系数是 1。对 10007 取模后结果仍为 1。
题目分析
经过简单的数学推导,不难得出以下结论。
给定一个多项式 P(x) = (x + a[1])(x + a[2])...(x + a[n]),其中 a[i] 是从输入的字符串中解析出的常数(即 +d 或 -d)。
当这个多项式展开后,会得到 n 个 x 的一次项。它们的系数之和,等于从这 n 个 a[i] 中任选 n-1 个进行相乘,然后将所有这些乘积相加的结果。
一种简单的思路如下:我们可以先计算出所有a[i]的累乘结果m,再将m分别除以各个a[i](相当于得到除了当前a[i]之外的n-1个数的乘积),然后将它们相加即可。
C++
#include <iostream>
#include <string>
#include <vector>
int main() {
std::string expr;
std::cin >> expr;
int64_t m = 1;
std::vector<int> nums;
for (auto i = 0; i < expr.size(); i += 5) {
int num = expr[i + 3] - '0';
if (expr[i + 2] == '-') {
num = -num;
}
m *= num;
nums.push_back(num);
}
if (nums.size() == 1) {
std::cout << 1;
return 0;
}
int64_t ans = 0;
for (int i = 0; i < nums.size(); ++i) {
ans += m / nums[i];
}
std::cout << ans % 10007;
}
但这么做有一个严重的问题。题目中s的长度可以达到10^5,也就是说输入的表达式中最多有10^5/5=20000个(x±a[i])子串。即使这些a[i]的取值都为2,它们的乘积结果也高达2^20000。这是一个天文数字,远远超过了int64_t
能够表达的范围。
不过幸运的是,取模运算对加法、减法、乘法满足分配律:
- (a + b) % p == ((a % p) + (b % p)) % p
- (a - b) % p == ((a % p) - (b % p)) % p
- (a * b) % p == ((a % p) * (b % p)) % p
因此我们设计的算法,应该在计算加法和乘法的过程中,对中间结果立即进行取模,才能保证运算过程中始终不发生溢出。同时避免直接使用除法。
解答代码
暴力解法
C++
#include <iostream>
#include <vector>
constexpr int mod_base = 10007;
int main() {
std::string expr;
std::cin >> expr;
std::vector<int> nums;
for (int i = 0; i < expr.size() / 5; ++i) {
int num = expr[i * 5 + 3] - '0';
if (expr[i * 5 + 2] == '-') {
num = -num;
}
nums.push_back(num);
}
// 外层循环控制当前项中不选择nums中的哪个数
int ans = 0;
for (int i = 0; i < nums.size(); ++i) {
// 内层循环用于累乘nums中的其他数,得到当前项
int curr = 1;
for (int j = 0; j < nums.size(); ++j) {
if (j == i) continue;
curr = (curr * nums[j]) % mod_base;
}
ans = (ans + curr) % mod_base;
}
std::cout << ans % mod_base << std::endl;
}
优化解法
我们可以使用前缀积和后缀积,用空间换时间。
C++
#include <iostream>
#include <vector>
constexpr int mod_base = 10007;
int main() {
std::string expr;
std::cin >> expr;
std::vector<int> nums;
for (int i = 0; i < expr.size() / 5; ++i) {
int num = expr[i * 5 + 3] - '0';
if (expr[i * 5 + 2] == '-') {
num = -num;
}
nums.push_back(num);
}
// 计算前缀积
std::vector<int> prefix(nums.size());
prefix[0] = 1;
for (int i = 1; i < nums.size(); ++i) {
prefix[i] = (prefix[i - 1] * nums[i - 1]) % mod_base;
}
// 计算后缀积
std::vector<int> suffix(nums.size());
suffix[nums.size() - 1] = 1;
for (int i = nums.size() - 2; 0 <= i; --i) {
suffix[i] = (suffix[i + 1] * nums[i + 1]) % mod_base;
}
// 计算答案
int ans = 0;
for (int i = 0; i < nums.size(); ++i) {
ans += (prefix[i] * suffix[i]) % mod_base;
}
std::cout << ans % mod_base << std::endl;
}