【算法导论】一道涉及到溢出处理的笔试题

题目内容

小明给出了一个关于未知量 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能够表达的范围。

不过幸运的是,取模运算对加法、减法、乘法满足分配律:

  1. (a + b) % p == ((a % p) + (b % p)) % p
  2. (a - b) % p == ((a % p) - (b % p)) % p
  3. (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;
}
相关推荐
执着25913 分钟前
力扣hot100 - 94、二叉树的中序遍历
数据结构·算法·leetcode
-dzk-16 分钟前
【代码随想录】LC 707.设计链表
数据结构·c++·算法·链表
Dave.B1 小时前
vtkPolyDataConnectivityFilter 实用指南
算法·vtk
细节处有神明1 小时前
开源数据之历史气象数据的获取与使用
人工智能·python·算法
小白开始进步2 小时前
JAKA Zu12 机械臂运动学算法深度解析(含可视化方案)
python·算法·numpy
梵刹古音2 小时前
【C语言】 递归函数
c语言·数据结构·算法
岁岁种桃花儿2 小时前
SpringCloud超高质量面试高频题300道题
spring·spring cloud·面试
yongui478342 小时前
混凝土二维随机骨料模型 MATLAB 实现
算法·matlab
酉鬼女又兒2 小时前
JAVA牛客入门11~20
算法