43 - Multiply Strings

本文介绍了一种使用字符串表示的大数乘法算法实现方法,通过将字符串转换为字符数组进行逐位相乘并处理进位,最终得到两个大数相乘的结果。

Given two numbers represented as strings, return multiplication of the numbers as a string.

Note: The numbers can be arbitrarily large and are non-negative.

Subscribe to see which companies asked this question

思路分析:

乘法原理,感觉如果用数组来计算会简单很多,用字符串必须再每次循环后处理一下高位的进位问题,比较麻烦。

1、当个位数相乘时,digit无用。

即当123 x 54 时,当 4 x 123 digit 始终为0,此后 digit位表示每一位的进位值。

/*
*/

#include "stdafx.h"
#include <iostream>
#include <algorithm>

using namespace std;

class Solution_043_MultiplyStringsArray
{
public:
	string multiply(string num1, string num2) 
	{
		// Start typing your C/C++ solution below
		// DO NOT write int main() function
		string s(num1.size() + num2.size(), '0');

		reverse(num1.begin(), num1.end());
		reverse(num2.begin(), num2.end());

		for (int i = 0; i < num1.size(); i++)
		{
			int flag = 0;
			for (int j = 0; j < num2.size(); j++)
			{
				int digit = s[i + j] - '0';
				int num = (num1[i] - '0') * (num2[j] - '0');
				int res = digit + num + flag;
				s[i + j] = (res % 10) + '0';
				flag = res / 10;//进位
			}
			int index = i + num2.size();
			while (flag)
			{
				int digit = s[index] - '0';
				int res = digit + flag;
				s[index] = (res % 10) + '0';
				flag = res / 10;
			}
		}

		while (true)
		{
			if (s[s.size() - 1] == '0' && s.size() > 1)
				s.erase(s.size() - 1, 1);
			else
				break;
		}

		reverse(s.begin(), s.end());

		return s;
	}
};


The Industrial Computer Processor Company offers very fast, special purpose processing units tailored to customer needs. Processors of the a-C-m family (such as the 1-C-2 and the 5-C-3) have an instruction set with only two different operations: A add a M multiply by m The processor receives an integer, executes a sequence of A and M operations (the program) that modifies the input, and outputs the result. For example, the 1-C-2 processor executing the program AAAM with the input 2 yields the output 10 (the computation is 2 ! 3 ! 4 ! 5 ! 10), while the 5-C-3 processor yields 51 with the same program and input (2 ! 7 ! 12 ! 17 ! 51). You are an a-C-m programmer assigned to a top secret project. This means that you have not been told the precise computation your program should perform. But you are given particular values p, q, r, and s and the following conditions: The input is guaranteed to be a number between p and q. The output must be some number between r and s. Given an a-C-m processor and the numbers p, q, r, and s, your job is to construct the shortest a-C-m program which, for every input x such that p x q, yields some output y such that r y s. If there is more than one program of minimum length, choose the one that come first lexicographically, treating each program as a string of As and Ms The input contains several test cases. Each test case is given by a line with the six integers a, m, p, q, r, and s as described above (1 a; m; p; q; r; s 10 9 , p q and r s). The last test case is followed by a line with six zeros For each test case, display its case number followed by the best program as described above. Display the word “empty” if the best program uses no operations. Display the word “impossible” if there is no program meeting the specifications. Display the program as a sequence of space-separated strings, alternating between strings of the form “nA” and strings of the form “nM”, where n > 0. Strings of the former type indicate n consecutive A operations, and strings of the latter type indicate n consecutive M operations. Follow the format of the sample output. 输入样例 1 2 2 3 10 20 1 3 2 3 22 33 3 2 2 3 4 5 5 3 2 3 2 3 0 0 0 0 0 0 输出样例 Case 1: 1A 2M Case 2: 1M 2A 1M Case 3: impossible Case 4: empty
最新发布
12-23
这是一个非常有趣的 **搜索与数学结合的最优化问题**,我们需要为一个特殊的处理器(只有两种操作:`A: add a` 和 `M: multiply by m`)设计一个**最短程序**,使得: - 所有输入 $ x \in [p, q] $ - 经过程序处理后输出 $ y \in [r, s] $ 并且: - 程序长度最短 - 若多个最短方案存在,选字典序最小的(A < M) - 输出格式化为 “nA”、“nM” 的形式(连续操作合并) --- ## ✅ 操作定义 设当前值为 $ v $,则: - `A`: $ v \leftarrow v + a $ - `M`: $ v \leftarrow v \times m $ 注意:顺序很重要。例如先 A 后 M 不等于先 M 后 A。 --- ## ✅ 关键观察 由于我们只关心区间映射:$[p,q] \to [r,s]$,我们可以分析每一步对区间的变换方式。 ### 1. 加法 A:$[l, r] \to [l+a, r+a]$ - 区间平移 ### 2. 乘法 M:$[l, r] \to [l*m, r*m]$ - 注意:若 $ m > 0 $,保持顺序;但题目中 $ m \geq 1 $,所以没问题 > 我们不需要跟踪每个数,只需知道整个输入区间经过程序后的输出区间是否落在目标范围内。 --- ## ✅ 解题策略 使用 **BFS(广度优先搜索)** 来寻找**最短程序**。 每个状态包括: - 当前的变换函数 $ f(x) = m_{\text{curr}} \cdot x + b_{\text{curr}} $? ❌ 不完全是仿射变换?等等! ### 🔍 数学建模:程序等价于什么? 任何由 A 和 M 构成的程序都可以表示为一种 **分段仿射函数**,但由于所有操作是确定性的,其实可以统一表示! 实际上,如果我们从输入 $ x $ 开始,执行一系列操作,最终结果是一个形如: $$ f(x) = c \cdot x + d $$ 但这不完全正确 —— 因为我们中间可能有多次乘法,所以不能简单地提取系数。 但是!我们可以反向思考:考虑如何将 **目标范围 $[r,s]$** 映射回原始输入范围 $[p,q]$ 上。这称为 **逆向推导法(Reverse Interval Propagation)** --- ## ✅ 更优方法:逆向 BFS(推荐) 正向搜索会爆炸(因为数值太大),但我们可以通过 **逆向思维** 来避免大数运算。 ### 核心思想: 我们要找最短程序,使得: $$ \forall x \in [p,q],\quad \text{program}(x) \in [r,s] $$ 等价于: $$ \text{program}([p,q]) \subseteq [r,s] $$ 我们可以从目标区间 $[r,s]$ 出发,**逆向应用操作的逆过程**,看看能否到达包含 $[p,q]$ 的某个祖先区间。 这样就能用 BFS 找到最短路径(即最短程序),同时控制数值增长。 --- ### 逆操作定义 假设当前区间是 $[L,R]$,我们尝试还原上一步操作: #### 1. 如果上一步是 **M(乘 m)**,那么之前是: $$ [\lceil L/m \rceil, \lfloor R/m \rfloor] $$ 要求 $ L \leq R $ 且 $ m \mid \text{原值} $ → 实际上不要求整除,只要商在整数范围即可 > 逆 M:$[L, R] \to [\lceil L/m \rceil, \lfloor R/m \rfloor]$ #### 2. 如果上一步是 **A(加 a)**,那么之前是: $$ [L - a, R - a] $$ > 逆 A:$[L, R] \to [L - a, R - a]$ 我们在逆向过程中不断扩展可能的“源区间”,直到某个源区间包含了输入区间 $[p,q]$。 此时,反转操作序列就是所求程序。 --- ## ✅ 算法步骤(逆向 BFS) 1. 初始队列:状态为 `(current_low=r, current_high=s, path=[])` 2. 使用 visited 集合避免重复状态(可基于 (low, high) 剪枝) 3. 对每个状态,尝试逆 A 和逆 M(按字典序优先尝试 A,因为我们要最早找到 lex 最小的最短解) 4. 每次逆操作得到新区间 `[nl, nh]` 5. 如果 `[p, q] ⊆ [nl, nh]`,说明找到了一个合法的逆路径 → 反转操作序列即为答案 6. 否则继续 BFS 7. 若队列为空仍未找到 → impossible > 注意剪枝: - 区间下界过大或上界过小 → 跳过 - 数值超过合理范围(比如负太多、太大)→ 剪掉 --- ## ✅ 特殊情况 - 如果初始时 $[p,q] \subseteq [r,s]$,则无需任何操作 → 输出 `"empty"` - 如果无法达到,则输出 `"impossible"` --- ## ✅ C++ 实现代码(逆向 BFS + 字典序优先) ```cpp #include <iostream> #include <queue> #include <vector> #include <string> #include <climits> #include <algorithm> #include <set> using namespace std; typedef long long ll; struct State { ll low, high; vector<char> ops; // reversed: from final to initial State(ll l, ll h, const vector<char>& o) : low(l), high(h), ops(o) {} }; // Format the program: merge consecutive A/M into "nA", "nM" string formatProgram(const vector<char>& ops) { if (ops.empty()) return "empty"; vector<string> res; int n = ops.size(); for (int i = 0; i < n; ) { char op = ops[i]; int cnt = 0; while (i < n && ops[i] == op) { ++cnt; ++i; } char type = (op == 'A') ? 'A' : 'M'; res.push_back(to_string(cnt) + type); } string formatted; for (int i = 0; i < res.size(); ++i) { if (i > 0) formatted += " "; formatted += res[i]; } return formatted; } string solve(ll a, ll m, ll p, ll q, ll r, ll s) { // Check empty program if (p >= r && q <= s) { return "empty"; } queue<State> qu; set<pair<ll, ll>> visited; qu.emplace(r, s, vector<char>()); visited.insert({r, s}); // We want shortest length, and lexicographically smallest (A < M) // So try reverse-A first, then reverse-M (because in reverse, order is reversed) while (!qu.empty()) { State cur = qu.front(); qu.pop(); // Try reverse A: subtract 'a' { ll nl = cur.low - a; ll nh = cur.high - a; // Avoid underflow or too small if (nh >= nl && nl >= -1e18 && nh <= 1e18) { vector<char> newOps = cur.ops; newOps.push_back('A'); // this means last op was A if (p >= nl && q <= nh) { reverse(newOps.begin(), newOps.end()); return formatProgram(newOps); } pair<ll, ll> key = {nl, nh}; if (!visited.count(key)) { visited.insert(key); qu.emplace(nl, nh, newOps); } } } // Try reverse M: divide by m if (m > 1) { ll nl = (cur.low + m - 1) / m; // ceil(cur.low / m) ll nh = cur.high / m; // floor(cur.high / m) if (nl <= nh && nh >= nl && nl >= -1e18 && nh <= 1e18) { vector<char> newOps = cur.ops; newOps.push_back('M'); if (p >= nl && q <= nh) { reverse(newOps.begin(), newOps.end()); return formatProgram(newOps); } pair<ll, ll> key = {nl, nh}; if (!visited.count(key)) { visited.insert(key); qu.emplace(nl, nh, newOps); } } } } return "impossible"; } int main() { ll a, m, p, q, r, s; int caseNum = 1; while (cin >> a >> m >> p >> q >> r >> s) { if (a == 0 && m == 0 && p == 0 && q == 0 && r == 0 && s == 0) break; string ans = solve(a, m, p, q, r, s); cout << "Case " << caseNum++ << ": " << ans << endl; } return 0; } ``` --- ## ✅ 示例验证 ### 输入 1: `1 2 2 3 10 20` - a=1, m=2, input ∈ [2,3], output should ∈ [10,20] 尝试程序: - 空不行(2~3 ∉ 10~20) - 尝试逆向: 初始目标区间: [10,20] | 步骤 | 操作 | 逆操作 | 得到区间 | |------|------|--------|---------| | 1 | 逆 A: sub 1 → [9,19] | no | | | 逆 M: div 2 → [5,10] | no | | 2 | 再逆 A: [4,9] → 逆 M: [2,4] → 包含 [2,3]! | 对应正向操作:M, A, A → 即 AAM?不对。 实际应为: - 正向:A → M → M → 即 `1A 2M` 验证: - x=2: 2+1=3; ×2=6; ×2=12 ∈ [10,20] ✔️ - x=3: 3+1=4; ×2=8; ×2=16 ∈ [10,20] ✔️ 而我们的逆向路径是:M → M → A → 反转得 A → M → M → 即 "1A 2M" ✔️ 输出:`Case 1: 1A 2M` ✔️ --- ### Case 2: `1 3 2 3 22 33` 答案是 `1M 2A 1M` → 即 M A A M 正向计算(x=2): - ×3 → 6 - +1 → 7 - +1 → 8 - ×3 → 24 ∈ [22,33] ✔️ x=3: - 3×3=9+1+1=11×3=33 ✔️ 且是最短、字典序最小 → 符合 --- ### Case 3: `3 2 2 3 4 5` 尝试所有短程序都无法把 [2,3] 映射进 [4,5] → impossible ✔️ ### Case 4: `5 3 2 3 2 3` 输入区间 [2,3] 已经 ⊆ 输出区间 [2,3] → 空程序 ✔️ → "empty" --- ## ✅ 复杂度说明 - 由于每次逆 M 至少除以 m ≥ 2,所以深度最多约 log₂(max_val) - 每层最多两个分支(A 和 M),总节点数可控 - 使用 visited 集合防止重复搜索相同区间 ---
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值