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

题目内容

小明给出了一个关于未知量 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 + a1)(x + a2)...(x + an),其中 ai 是从输入的字符串中解析出的常数(即 +d 或 -d)。

当这个多项式展开后,会得到 n 个 x 的一次项。它们的系数之和,等于从这 n 个 ai 中任选 n-1 个进行相乘,然后将所有这些乘积相加的结果。

一种简单的思路如下:我们可以先计算出所有ai的累乘结果m,再将m分别除以各个ai(相当于得到除了当前ai之外的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±ai)子串。即使这些ai的取值都为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;
}
相关推荐
梦帮科技1 分钟前
【3.0修订版】创世与节点工程:genesis.json 与 node.toml 解剖
数据结构·算法·json·去中心化·区块链·智能合约·信任链
影视飓风TIM10 分钟前
Linux 进程虚拟地址空间详解
linux·算法
如何原谅奋力过但无声14 分钟前
【灵神高频面试题合集21-25】动态规划(下)
算法·动态规划
释厄62323 分钟前
学术引用本体论——WorkBuddy 学术科学文化三违反
网络·人工智能·算法
梦帮科技25 分钟前
【3.0修订版】 RNS 代币架构:ERC20 五件套扩展与六钱包分配
数据结构·后端·算法·架构·node.js·区块链·php
薛定e的猫咪43 分钟前
(AISTATS 2023)BaCaDI:未知干预下的贝叶斯因果发现
人工智能·深度学习·算法·机器学习
ShineWinsu1 小时前
对于Redis:RDB持久化的解析
linux·数据库·redis·缓存·面试·持久化·rdb
漂流瓶jz2 小时前
UVA-11491 奖品的价值 题解答案代码 算法竞赛入门经典第二版
c++·算法·图论·题解·aoapc·算法竞赛入门经典·uva
weixin_307779136 小时前
C++代码实现MATLAB中的dlarray函数功能
开发语言·c++·算法·matlab
丑陋小蚊子7 小时前
项目数据不能公开,就写清交付物
面试·求职招聘·大学生·简历·简历下载