121. 分糖果
题目
问题描述
肖恩和帕特里克是兄弟,他们从他们的父母那里得到了很多糖果。每一块糖具有一个正整数的价值,孩子们希望分他们得到的糖果。首先,肖恩将这些糖果分成两堆,并选择一堆给帕特里克。然后,帕特里克将尝试计算每堆的价值,其中每堆的价值是那堆糖果价值的总和,如果他觉得没有平等的价值,他将开始哭了起来。
不幸的是,帕特里克太小了,所以不能正确的计算。他只会二进制无进位的加法。比如说,他想算12(二进制为1100)加5(二进制为101),他会把最右边的两位加法算正确,但是第三位会忘记进位。(即0+0=0,0+1=1,1+0=1,1+1=0)
因此,帕特里克算12加5的结果为9。下面几个是帕特里克算的结果:
5 + 4 = 1
7 + 9 = 14
50 + 10 = 56
肖恩数学很好,他想得到价值总和更高的糖果并且不让他的弟弟哭。如果可能,他会分成两个非空的糖果袋,让帕特里克认为,双方都有相同的值的糖果。给你每一袋糖果每一块糖果的价值,我们想知道是否可能让帕特里克相信他们得到糖果价值的总量是相同的。如果可能计算出肖恩能得到的最大的价值。
输入说明
第一行输入T(1<T<10),表示接下来输入T组测试数据。
每组测试数据占一行,每行包含以下数据,N C1 C2 .. Cn(其中N(2 ≤ N ≤ 10)代表从父母那里得到糖果的总数,C(1 ≤ Ci ≤ 100)代表每块糖果的价值)
输出说明
若不能输出NO,若能则输出肖恩得到的最大的价值。
个人总结
又是一道涉及新知识点的题目,而且还包括了数学知识,知道的话就很简单,不知道就只能暴力解。
思路
1. 概念转化:"帕特里克的加法"本质是什么?
题目中提到帕特里克的加法规则是:"二进制无进位的加法(0+0=0, 0+1=1, 1+0=1, 1+1=0)"。 如果你熟悉位运算,会立刻发现,这个规则完全等价于按位异或(XOR,C++ 中的 ^ 运算符)。
- 相同为 0,不同为 1。
- 因此,帕特里克眼中的"价值总和",实际上就是这堆糖果价值的异或和。
2. 结论推导:帕特里克不哭的条件是什么?
假设肖恩将糖果分成了两堆,集合为 A 和集合为 B。
- 帕特里克认为 A 堆的价值是:
XOR_SUM(A) - 帕特里克认为 B 堆的价值是:
XOR_SUM(B)
为了不让帕特里克哭,两堆的价值在他眼里必须相等,即: XOR_SUM(A) == XOR_SUM(B)
根据异或运算的性质(两个相同的数异或结果为 0,即 X ^ X = 0),我们可以将等式两边同时异或上 XOR_SUM(B),得到: XOR_SUM(A) ^ XOR_SUM(B) == 0
而 XOR_SUM(A) ^ XOR_SUM(B) 实际上就等于所有糖果放在一起的总异或和(因为异或满足交换律和结合律)。
- 推论 1: 如果所有糖果的总异或和不为 0 ,那么绝对不可能分成异或和相等的两堆。此时应该输出
NO。 - 推论 2: 如果所有糖果的总异或和为 0 ,那么任意一种 合法的分法(只要两堆都不为空),都会满足
XOR_SUM(A) == XOR_SUM(B)!
3. 肖恩的贪心策略:如何实现价值最大化?
肖恩的数学很好,他计算的是真正的十进制算术加法总和。 由前面的推论 2 可知,只要总异或和为 0,肖恩随便怎么分,帕特里克都会觉得两边一样多。
题目要求两堆都必须是"非空"的糖果袋。为了让肖恩得到的实际价值最大,他需要:
- 给帕特里克尽可能少的糖果。
- 给出的糖果实际价值尽可能小。
因此,最极端的做法就是:肖恩只给帕特里克 1 块价值最小的糖果,剩下的所有糖果全归自己。
知识点 - 异或运算
C++ 中有专门的异或运算符,完全不需要自己写函数实现;输入的十进制数字也完全不需要手动转换,直接算就行
1. C++ 中的专属异或运算符:^
在 C++ 中,异或操作有一个专门的内置运算符:键盘上的脱字符 ^ (通常是 Shift + 6)。 你不需要引入任何特殊的头文件,也不需要手写转换函数,直接像用加号 + 一样使用它即可。
代码中,有这样一行:
cpp
xor_sum ^= c;
这就等同于 xor_sum = xor_sum ^ c;。它的意思是:把 xor_sum 和新输入的糖果价值 c 进行异或运算,然后把结果重新存回 xor_sum 中。
除了异或 ^,C++ 还有其他的位运算符,可以直接使用:
| 运算符 | 名称 | 作用 | 举例 |
|---|---|---|---|
^ |
按位异或 | 相同为0,不同为1 | 12 ^ 5 结果为 9 |
& |
按位与 | 两个都为1才是1 | 12 & 5 结果为 4 |
| ` | ` | 按位或 | 有一个为1就是1 |
~ |
按位取反 | 0变1,1变0 | ~12 |
🔄 2. 为什么不需要把十进制转换成二进制?
这是很多初学者都会有的思维误区,觉得"既然是二进制无进位加法,我就得先写个 while 循环除以 2 取余数,把十进制转成二进制数组,然后再一位一位去算"。但是没必要。
不需要转换的原因在于:计算机底层全都是二进制。
当你写下 cin >> c; 并且输入一个十进制数字 12 时,发生了什么?
- 输入阶段: C++ 的底层机制已经自动把十进制的
12转换成了二进制的0000...1100,并存储在内存里。 - 运算阶段: 当你执行
12 ^ 5时,CPU 是直接拿内存里现成的二进制1100和0101进行极其底层的电平运算,瞬间得出1001。 - 输出阶段: 当你执行
cout打印结果时,C++ 又会自动把底层的二进制1001转换回十进制的9显示在屏幕上。
总结来说: 十进制、十六进制等仅仅是"给人类看"的表现形式,变量在 C++ 内存里永远都是以二进制形式存在的 。位运算符(^, &, |)就是专门设计用来直接操作内存里的这些二进制位的。
所以在做题时,你只需要大胆地把十进制变量直接拿去 ^,剩下的二进制转换工作全部交给 C++ 和 CPU 自动完成即可。
代码
cpp
#include <bits/stdc++.h>
using namespace std;
int main() {
ios::sync_with_stdio(0);
cin.tie(0);
int T;
cin >> T;
while (T--) {
int N;
cin >> N;
vector<int> cds;
for (int i = 0; i < N; i++) {
int n;
cin >> n;
cds.push_back(n);
}
int xor_sum = cds[0];
for (int i = 1; i < N; i++) {
xor_sum ^= cds[i];
}
if (xor_sum == 0) {
sort(cds.begin(),cds.end());
int sum = 0;
for (int i = 1; i < N; i++) sum += cds[i];
cout << sum << "\n";
} else {
cout << "NO\n";
}
}
return 0;
}
122. 循环数
题目
问题描述
循环数是那些不包括0这个数字的没有重复数字的整数 (比如说, 81362) 并且同时具有一个有趣的性质, 就像这个例子:
如果你从最左边的数字( 记为n,在这个例子中是8) 开始往右边数,一直数n个数字(如果已经到了最右边则回到最左边),你会停在另一个不同的数字上。如果停在一个相同的数字上,这个数就不是循环数。
就像: 8 1 3 6 2 从最左边接下去数8个数字: 1 3 6 2 8 1 3 6 所以下一个数字是6. 重复这样做 (这次从'6'开始数6个数字) 并且你会停止在一个新的数字上: 2 8 1 3 6 2, 也就是2. 再这样做 (这次数两个): 8 1。 再一次 (这次数一个): 3。 又一次: 6 2 8, 这时你回到了起点。
此时,我们数到的数字依次是:8 6 2 1 3,即每一个数字都被数了1次,并且回到了起点。
如果你将每一个数字都数了1次以后没有回到起点, 你的数字不是一个循环数。
给你一个数字 M (在1到9位之间), 找出第一个比 M大的循环数(输入的M保证这个循环数能保存在4位的有符号整数中)。
输入说明
仅仅一行, 包括M
输出说明
仅仅一行,包括第一个比M大的循环数。
个人总结
思路
- 从 M+1 开始,无限循环,逐个检查整数 n 是否为循环数。
- 将整数 n 转换为字符串 s,以便于按位操作。
- 检查 s 中是否包含重复数字。若有,则 n 不是循环数,检查下一个数。
- 检查 s 是否满足循环条件。 a. 使用一个 visited 数组记录每个位置是否被访问过。 b. 从第 0 位开始,根据当前位的数字 d,计算下一个位置 (idx + d) % len。 c. 在跳转过程中,如果发现下一个位置已经被访问过,则说明未遍历所有数字就产生了重复访问,不是循环数。 d. 遍历 len-1 次后,检查最后一次跳转是否能回到起点(位置 0)。如果不能,则不是循环数。
- 如果以上所有检查都通过,则 n 是第一个大于 M 的循环数,输出并结束程序。
易错点
- 循环规则的理解:是根据数字的值(n[idx] - '0')来决定步长,而不是位置索引。
- 循环终止的判断:整个循环过程需要恰好访问完所有数字,并且最后一步必须回到起点。代码中将最后一步的判断与前面 len-1 次的循环分开了,确保了"回到起点"这个条件。
- 取模运算:移动位置时,(当前位置 + 步长) % 长度,是处理"从最右边回到最左边"的关键。
- 题目要求是找到"第一个比 M 大的循环数"。如果从
n = M开始循环,而 M 本身恰好是一个循环数,程序会错误地输出 M。循环应该从M + 1开始。
代码
cpp
#include <bits/stdc++.h>
using namespace std;
bool repeat(const string &n) {
// 判断数字内是否含有重复数字
for (int i = 0; i < n.size(); i++) {
for (int j = i + 1; j < n.size(); j++) {
if (n[i] == n[j]) {
return true;
}
}
}
return false;
}
bool have0(const string &n) {
for (int i = 0; i < n.size(); i++) {
if (n[i] == '0') return true;
}
return false;
}
bool circle(const string &n) {
// 判断数字是否构成循环
int len = n.size();
int idx = 0;
vector<bool> visited(len, false);
int cnt = 0;
int d;
// 开始逐位循环
while (cnt < len - 1) {
visited[idx] = true;
d = n[idx] - '0';
idx = (idx + d) % len;
if (visited[idx]) return false;
cnt++;
}
// 最后一次循环需要回到起点
d = n[idx] - '0';
idx = (idx + d) % len;
if (idx != 0) return false;
return true;
}
int main() {
int M;
cin >> M;
int n = M + 1;
while (true) {
string s = to_string(n);
// 判断重复
if (repeat(s) || have0(s)) {
n++;
continue;
}
// 判断循环
if (circle(s)) {
cout << s << "\n";
break;
} else {
n++;
}
}
return 0;
}
123. 棋盘游戏
题目
问题描述
大小为3的棋盘游戏里有3个白色棋子,3个黑色棋子,和一个有7个格子一线排开的木盒子。3个白棋子被放在一头,3个黑棋子被放在另一头,中间的格子空着。
初始状态: WWW_BBB
目标状态: BBB_WWW
在这个游戏里有两种移动方法是允许的:
你可以把一个棋子移到与它相邻的空格;
你可以把一个棋子跳过一个(仅一个)与它不同色的棋子到达空格。
大小为N的棋盘游戏包括N个白棋子,N个黑棋子,还有有2N+1个格子的木盒子。
这里是3-棋盘游戏的解,包括初始状态,中间状态和目标状态:
WWW BBB
WW WBBB
WWBW BB
WWBWB B
WWB BWB
W BWBWB
WBWBWB
BW WBWB
BWBW WB
BWBWBW
BWBWB W
BWB BWW
B BWBWW
BB WBWW
BBBW WW
BBB WWW
请编一个程序解大小为N的棋盘游戏(1 <= N <= 12)。要求用最少的移动步数实现。
输入说明
一个整数N。
输出说明
用空格在棋盘的位置(位置从左到右依次为1, 2, ..., 2N+1)表示棋盘的状态。输出棋盘的状态变换序列,每行20个数(除了最后一行)。 输出的解还应当有最小的字典顺序(即如果有多组移动步数最小的解,输出第一个数最小的解;如果还有多组,输出第二个数最小的解;...)。
个人总结
又是一道对新手非常深邃的题目,也是目前为止第一道 BFS 章节的题目。
虽然本题写的代码在性能上还有很大的优化空间,但是考虑到初次接触本题,一堆新东西已经够麻烦了,就不再引入额外的复杂度了,再多记不住了。
对于本题的状况,这套思路和代码是能 AC 的。
思路
这道题是一个非常经典的棋盘游戏谜题,通常被称为"青蛙跳"或类似的名称。它本质上是一个状态空间搜索问题,目标是找到从初始状态到目标状态的最短路径。解决这个问题的核心思想是广度优先搜索(Bread-First Search, BFS)。
思想与原理
1. 状态表示
首先,我们需要一种方式来表示棋盘的每一种布局。由于棋盘是一维的,一个简单的字符串或字符数组就是最直观的表示方法。例如,"WWW_BBB" 就代表了初始状态。
2. 状态转移
游戏规则定义了状态之间如何转换。从任何一个状态(棋盘布局),我们可以通过合法的移动(棋子移动到相邻空格,或跳过一个异色棋子到空格)到达一系列新的状态。这些新状态就是当前状态在"状态图"中的邻居。
- 滑步 (Slide):一个棋子移动到紧邻的空格。
- 跳步 (Jump):一个棋子跳过一个(且仅一个)不同颜色的棋子,落到空格里。
3. 寻找最短路径:广度优先搜索 (BFS)
题目要求用最少的移动步数完成,这马上就让我们想到广度优先搜索(BFS)算法。BFS 的特点是它会一层一层地探索所有可能的状态。
- 它从初始状态开始,首先找到所有仅需 1 步就能到达的状态。
- 然后,再从这些状态出发,找到所有仅需 2 步就能到达的新状态。
- 这个过程持续下去,直到找到目标状态
BBB_WWW为止。
因为 BFS 是逐层扩展的,所以它保证了当第一次找到目标状态时,所经过的路径(移动步数)一定是最短的。
4. 避免重复搜索
在搜索过程中,我们可能会多次遇到同一个棋盘布局。为了避免重复计算和陷入死循环,我们需要一个机制来记录哪些状态已经被访问过。通常,我们可以使用一个集合(比如 C++ STL 中的 std::set 或 std::unordered_set)来存储所有已经访问过的状态字符串。在生成一个新状态后,我们先检查它是否已经被访问过,只有当它是一个全新的状态时,才将它加入到待处理的队列中。
5. 记录路径
为了能够输出完整的移动步骤,光找到目标状态是不够的,我们还需要知道是怎么到达的。一个常用的方法是使用一个映射(比如 C++ STL 中的 std::map),用来记录每个状态的"父状态"。
map<string, string> parent;- 当我们从状态
S1移动到新状态S2时,我们就在 map 中记录parent[S2] = S1。 - 找到目标状态后,我们就可以通过这个 map 从目标状态一路回溯到初始状态,从而重建整个最短路径。
算法实现步骤
以下是解决这个问题的具体算法步骤:
-
初始化:
- 创建一个队列,并将初始状态(例如
"WWW_BBB")入队。 - 创建一个集合
visited,用来存放已访问过的状态,并将初始状态加入visited。 - 创建一个映射
parent,用于追溯路径。
- 创建一个队列,并将初始状态(例如
-
循环搜索:
- 当队列不为空时,从队列中取出一个状态作为当前状态
current_state。 - 如果
current_state等于目标状态,则搜索成功。通过parent映射从目标状态回溯至初始状态,得到完整路径,然后打印并结束程序。 - 如果不是目标状态,则根据移动规则,从
current_state生成所有可能的下一个状态next_state:- 找到
current_state中空格_的位置。 - 检查所有可能的"滑步"和"跳步"操作,生成一系列
next_state。
- 找到
- 当队列不为空时,从队列中取出一个状态作为当前状态
-
处理新状态:
- 对于每一个生成的
next_state:- 检查它是否在
visited集合中。 - 如果不在 ,则说明这是一个新的、未曾探索过的状态。我们将它:
- 加入
visited集合。 - 记录它的父状态:
parent[next_state] = current_state。 - 将它加入队列,以待后续处理。
- 加入
- 检查它是否在
- 对于每一个生成的
知识点 - set
好的。std::set 是 C++ 标准模板库(STL)中的一种关联容器,它的核心特性是 所有元素都会根据元素的键值自动排序,并且每个元素都是唯一的。
set 的内部实现通常是红黑树,这使得它的插入、删除和查找操作的时间复杂度都能稳定在 O(log N)。
1. 头文件
要使用 set,需要包含头文件:
2. 声明和初始化
set 的声明方式如下:
cpp
// 声明一个存储 int 的 set,默认是升序排列
std::set<int> s;
// 声明一个存储 string 的 set
std::set<std::string> str_set;
// 声明一个降序排列的 int set
// std::greater<int> 是一个比较器
std::set<int, std::greater<int>> s_desc;
// 用数组初始化 set
int arr[] = {1, 2, 3, 2, 1, 4};
std::set<int> s_from_arr(arr, arr + 6); // s_from_arr 中会是 {1, 2, 3, 4}
3. 核心用法
(1) 插入元素
使用 insert() 方法插入元素,它会自动处理去重和排序。
cpp
std::set<int> s;
s.insert(10); // s: {10}
s.insert(5); // s: {5, 10}
s.insert(10); // s: {5, 10},插入重复元素,set 无变化
(2) 访问元素
set 不支持像数组那样通过下标 [] 访问,因为它的元素是排序的,而不是按插入顺序存储的。遍历是访问 set 元素的主要方式。
cpp
std::set<int> s = {10, 5, 15}; // s: {5, 10, 15}
// 使用基于范围的 for 循环 (C++11 及以后)
for (int val : s) {
// val 将依次是 5, 10, 15
}
// 使用迭代器
for (auto it = s.begin(); it != s.end(); ++it) {
// *it 将依次是 5, 10, 15
}
set 的第一个元素可以通过 *s.begin() 访问,最后一个元素可以通过 *s.rbegin() 或 *(--s.end()) 访问。
(3) 查找元素
find(value): 查找值为value的元素。如果找到,返回指向该元素的迭代器;如果找不到,返回s.end()。时间复杂度 O(log N)。count(value): 返回值为value的元素的个数。因为set元素唯一,所以返回值只可能是 0 或 1。这在 OJ 中是判断元素是否存在的快速方法。时间复杂度 O(log N)。
cpp
std::set<int> s = {10, 20, 30};
// find
auto it = s.find(20);
if (it != s.end()) {
// 找到了,it 指向 20
}
it = s.find(40);
if (it == s.end()) {
// 没找到
}
// count
if (s.count(20)) { // count(20) 返回 1
// 存在
}
if (!s.count(40)) { // count(40) 返回 0
// 不存在
}
(4) 删除元素
erase(value): 删除值为value的元素。返回删除的元素个数(0 或 1)。时间复杂度 O(log N)。erase(iterator): 删除迭代器指向的元素。时间复杂度均摊 O(1)。
cpp
std::set<int> s = {10, 20, 30};
s.erase(20); // s: {10, 30}
auto it = s.find(10);
if (it != s.end()) {
s.erase(it); // s: {30}
}
(5) lower_bound 和 upper_bound
这两个方法非常有用,它们返回的都是迭代器,时间复杂度 O(log N)。
lower_bound(value): 返回指向第一个 大于或等于value的元素的迭代器。upper_bound(value): 返回指向第一个 严格大于value的元素的迭代器。
如果找不到满足条件的元素,它们都会返回 s.end()。
cpp
std::set<int> s = {10, 20, 30, 40, 50};
// 查找 >= 25 的第一个元素
auto it_lower = s.lower_bound(25); // it_lower 指向 30
// 查找 > 20 的第一个元素
auto it_upper = s.upper_bound(20); // it_upper 指向 30
// 如果要找的元素大于 set 中所有元素
it_lower = s.lower_bound(60); // it_lower == s.end()
4. 其他常用方法
size(): 返回set中的元素个数。empty(): 判断set是否为空。clear(): 清空set中的所有元素。
知识点 - 队列
std::queue 是 C++ 标准模板库(STL)中提供的一个容器适配器,它实现了先进先出(First-In, First-Out, FIFO)的数据结构。你可以把它想象成现实生活中的排队,先来的人先得到服务。
queue 的行为是受限制的,只能在队尾添加元素,在队头移除元素。
1. 如何使用
要使用 queue,需要先包含头文件 <queue>。
cpp
#include <queue>
queue 的定义语法如下:
cpp
std::queue<ElementType> q;
其中 ElementType 是你想要存储在队列中的元素类型,例如 int, double, string 或自定义的结构体。
2. 核心成员函数
queue 提供的主要操作函数如下,它们的时间复杂度通常都是 O(1):.
| 函数 | 描述 |
|---|---|
push(element) |
在队尾插入一个元素。 |
pop() |
移除队头的元素(注意:此函数不返回任何值)。 |
front() |
返回队头元素的引用,即最早进入队列的元素。 |
back() |
返回队尾元素的引用,即最后进入队列的元素。 |
empty() |
检查队列是否为空。如果为空,返回 true,否则返回 false。 |
size() |
返回队列中元素的数量。 |
3. 用法示例
下面是一个简单的示例,展示了 queue 的基本用法:
cpp
#include <iostream>
#include <queue>
#include <string>
int main() {
// 创建一个存储 string 类型元素的队列
std::queue<std::string> tasks;
// 检查队列是否为空
if (tasks.empty()) {
std::cout << "任务队列初始为空。" << std::endl;
}
// 使用 push() 向队尾添加元素
tasks.push("任务A");
tasks.push("任务B");
tasks.push("任务C");
// 使用 size() 查看队列中的元素数量
std::cout << "现在队列中有 " << tasks.size() << " 个任务。" << std::endl;
// 使用 front() 和 back() 查看队头和队尾的元素
std::cout << "队头任务是: " << tasks.front() << std::endl;
std::cout << "队尾任务是: " << tasks.back() << std::endl;
// 循环处理并移除队列中的元素
std::cout << "\n开始处理任务..." << std::endl;
while (!tasks.empty()) {
// 1. 获取队头任务
std::string current_task = tasks.front();
std::cout << "正在处理: " << current_task << std::endl;
// 2. 移除队头任务
tasks.pop();
}
// 再次检查队列是否为空
if (tasks.empty()) {
std::cout << "\n所有任务处理完毕,队列现在为空。" << std::endl;
}
return 0;
}
代码
cpp
#include <bits/stdc++.h>
using namespace std;
queue<string> q;
set<string> vis;
map<string,string> pre;
void add_next(const string &cur, int pos, int off) {
// 创建下一个状态
string next = cur;
swap(next[pos], next[pos + off]);
// 如果下一个状态没有访问过,就加入
if (!vis.count(next)) {
vis.insert(next);
q.push(next);
pre[next] = cur;
}
}
int main() {
ios::sync_with_stdio(0);
cin.tie(0);
int N;
cin >> N;
// 生成初始状态和目标状态
string s = "";
string t = "";
for (int i = 0; i < N; i++) {
s += 'W';
t += 'B';
}
s += "_";
t += "_";
for (int i = 0; i < N; i++) {
s += 'B';
t += 'W';
}
// 将初始状态入队并标记访问
q.push(s);
vis.insert(s);
while (!q.empty()) {
string cur = q.front();
q.pop();
if (cur == t) {
// 打印路径
// 寻找路径
vector<int> path;
while (cur != s) {
int pos = cur.find('_');
path.push_back(pos);
cur = pre[cur];
}
// 倒序输出
int cnt = 0;
for (int i = path.size() - 1; i >= 0; i--) {
if (cnt % 20 == 0 && cnt != 0) {
cout << "\n";
}
if (cnt % 20 != 0) {
cout << " ";
}
// 实际输出要大 1
cout << path[i] + 1;
cnt++;
}
break;
}
int pos = cur.find('_');
// 生成所有可能的下一步
// WB_ -> _BW (白棋向右跳)
if (pos > 1 && cur[pos-2] == 'W' && cur[pos-1] == 'B') {
add_next(cur, pos, -2);
}
// W_ -> _W (白棋向右滑)
if (pos > 0 && cur[pos-1] == 'W') {
add_next(cur, pos, -1);
}
// _B -> B_ (黑棋向左滑)
if (pos < 2 * N && cur[pos+1] == 'B') {
add_next(cur, pos, 1);
}
// _WB -> BW_ (黑棋向左跳)
if (pos < 2*N-1 && cur[pos+1] == 'W' && cur[pos+2] == 'B') {
add_next(cur, pos, 2);
}
}
return 0;
}
计算机英语
翻译练习
Smartphones are the most commonly used digital device in the world. A smartphone features a small keyboard or touchscreen and is designed to fit into a pocket, run on batteries, and be used while you are holding it in your hands.
智能手机是全球最常用的数字设备。
智能手机配有小型键盘或触摸屏,设计为可放入口袋,由电池供电,并可在手持时使用。
Smartphones are equipped with built-in speech recognition that allows you to ask questions and control the device using spoken commands. Smartphones also include GPS capability so that apps are able to provide location-based services such as a route navigation map or a list of nearby restaurants.
智能手机内置语音识别功能,可以通过语音命令提问和控制设备。
此外,智能手机还配备GPS定位功能,因此应用程序能够提供基于位置的服务,例如路线导航地图或附近餐厅列表。
Smartphones evolved from basic cell phones and PDAs.PDA(personal digital assistant) was a handheld device used as an electronic appointment book, calculator, and notepad. Modern smartphones include a similar suite of applications, but they also have access to a huge variety of mobile apps that help you calculate tips, play your favorite music, and entertain you with games.
智能手机是从早期的普通手机和PDA发展而来的。
PDA(个人数字助理)是一种手持设备,用作电子日程本、计算器和记事本。
如今的智能手机同样具备类似的应用程序套件,但它们还能访问海量的移动应用,帮助你计算小费、播放喜爱的音乐,以及通过游戏为你带来娱乐。
背单词打卡
