在 C++ 中,表达式 (i & 1) 是一个位运算表达式。它使用了按位与操作符 & 来对变量 i 和数字 1 进行二进制的按位与操作。
按位与操作符 & 会比较两个数的二进制表示中的对应位,如果两个相应的二进制位都为 1,则该位的结果值为 1,否则为 0。
由于数字 1 的二进制表示只在最低位(也就是最右边的位)为 1,其他位都是 0,所以 (i & 1) 这个表达式的结果就是检查变量 i 的最低位是否为 1:
-
如果 i 的最低位是 1,那么 (i & 1) 的结果就是 1。
-
如果 i 的最低位是 0,那么 (i & 1) 的结果就是 0。
因此,(i & 1) 可以用来判断 i 是否是奇数。如果结果是 1,那么 i 是奇数;如果结果是 0,那么 i 是偶数。
快速幂是一种高效的算法,用于计算一个数的指数次幂。在 C++ 中,可以使用递归或迭代的方式来实现快速幂。
迭代代码示例:
cpp
#include <iostream>
using namespace std;
long long quickPow(long long base, long long exponent) {
long long result = 1;
while (exponent > 0) {
if (exponent & 1) { // 如果指数是奇数,则将当前底数乘入结果中
result *= base;
}
base *= base; // 底数平方
exponent >>= 1; // 指数右移一位(相当于除以2)
}
return result;
}
int main() {
long long base, exponent;
cout << "请输入底数和指数:" << endl;
cin >> base >> exponent;
cout << base << " 的 " << exponent << " 次幂为:" << quickPow(base, exponent) << endl;
return 0;
}
在 C++ 中,位移运算符包括左移运算符 << 和右移运算符 >>。
- 左移运算符 <<
左移运算符 << 用于将一个数的二进制表示向左移动指定的位数。具体来说,x << n 的操作是将 x 的二进制表示向左移动 n 位,同时将最右边的 n 位移出,并在最左边补上 n 个 0。
例如,假设有一个整数 x,其二进制表示为 101(十进制中的 5),那么 x << 1 的结果就是 1010(十进制中的 10)。
需要注意的是,当使用左移运算符时,需要注意数值类型的范围和溢出的问题。对于有符号整数类型,左移操作会保留符号位,即正数的符号位为 0,负数的符号位为 1。而对于无符号整数类型,左移操作不会保留符号位,而是直接在最左边补上 0。
- 右移运算符 >>
右移运算符 >> 用于将一个数的二进制表示向右移动指定的位数。具体来说,x >> n 的操作是将 x 的二进制表示向右移动 n 位,同时将最左边的 n 位移出,并在最右边补上 n 个 0。
例如,假设有一个整数 x,其二进制表示为 1010(十进制中的 10),那么 x >> 1 的结果就是 0101(十进制中的 5)。
需要注意的是,当使用右移运算符时,需要注意数值类型的范围和符号位的处理。对于有符号整数类型,右移操作会保留符号位,即正数的符号位为 0,负数的符号位为 1。而对于无符号整数类型,右移操作不会保留符号位,而是直接在最左边补上 0。
另外,左移操作相当于将原数乘以2的n次方,其中 n 是左移的位数。因此,可以使用左移运算符来高效地进行乘法运算。但需要注意的是,如果左移的位数过大,可能会导致溢出或者超出数值类型的范围,从而得到错误的结果。
代码示例:
cpp
#include <iostream>
using namespace std;
int main() {
int x = 5; // 二进制表示为 101
cout << "x << 1: " << (x << 1) << endl; // 输出 10,即 1010
cout << "x >> 1: " << (x >> 1) << endl; // 输出 2,即 10
return 0;
}
在 C++ 中,按位运算符包括以下几种:
- 按位与运算符 &
按位与运算符 & 用于对两个数的二进制表示进行按位与操作。具体来说,对于两个二进制数 a 和 b,它们的按位与操作的结果是将它们每一位上的值进行逻辑与操作,即如果两个数在该位上都是 1,则结果为 1,否则为 0。
例如,假设有两个整数 x 和 y,它们的二进制表示分别为 1010 和 1100,那么 x & y 的结果就是 1000(十进制中的 8)。
需要注意的是,按位与操作只能用于整数类型,不能用于浮点数类型。此外,按位与操作的结果也是一个整数,因此需要注意数值类型的范围和溢出的问题。
- 按位或运算符 |
按位或运算符 | 用于对两个数的二进制表示进行按位或操作。具体来说,对于两个二进制数 a 和 b,它们的按位或操作的结果是将它们每一位上的值进行逻辑或操作,即如果两个数在该位上至少有一个为 1,则结果为 1,否则为 0。
例如,假设有两个整数 x 和 y,它们的二进制表示分别为 1010 和 1100,那么 x | y 的结果就是 1110(十进制中的 14)。
需要注意的是,按位或操作只能用于整数类型,不能用于浮点数类型。此外,按位或操作的结果也是一个整数,因此需要注意数值类型的范围和溢出的问题。
- 按位异或运算符 ^
按位异或运算符 ^ 用于对两个数的二进制表示进行按位异或操作。具体来说,对于两个二进制数 a 和 b,它们的按位异或操作的结果是将它们每一位上的值进行逻辑异或操作,即如果两个数在该位上相同,则结果为 0,否则为 1。
例如,假设有两个整数 x 和 y,它们的二进制表示分别为 1010 和 1100,那么 x ^ y 的结果就是 0110(十进制中的 6)。
需要注意的是,按位异或操作只能用于整数类型,不能用于浮点数类型。此外,按位异或操作的结果也是一个整数,因此需要注意数值类型的范围和溢出的问题。
- 按位取反运算符 ~
按位取反运算符 ~ 用于对一个数的二进制表示进行按位取反操作。具体来说,对于一个二进制数 a,它的按位取反操作的结果是将其每一位上的值进行逻辑非操作,即如果该位上的值为 1,则结果为 0,否则为 1。
例如,假设有一个整数 x,其二进制表示为 1010,那么 ~x 的结果就是 0101(十进制中的 -11)。
需要注意的是,按位取反操作只能用于整数类型,不能用于浮点数类型。此外,按位取反操作的结果也是一个整数,因此需要注意数值类型的范围和溢出的问题。
代码示例:
cpp
#include <iostream>
using namespace std;
int main() {
int x = 5, y = 3; // 二进制表示分别为 101 和 011
cout << "x & y: " << (x & y) << endl; // 输出 1,即 001
cout << "x | y: " << (x | y) << endl; // 输出 7,即 111
cout << "x ^ y: " << (x ^ y) << endl; // 输出 6,即 110
cout << "~x: " << (~x) << endl; // 输出 -6,即 1010(补码表示)
return 0;
}
倍增思想是一种优化算法,用于解决某些问题的时间复杂度。它的基本思想是将问题分解为多个子问题,然后通过合并子问题的解来得到原问题的解。在计算机科学中,倍增思想常用于解决区间查询问题,如求区间最小值、最大值、区间和等。
下面以求解区间最小值为例,介绍倍增思想的实现过程:
-
首先,我们需要预处理出每个节点的父节点和深度信息。假设我们有一个数组a,长度为n,我们可以使用一个二维数组pre[i][j]来存储a[i]到a[j]之间的最小值。其中,pre[i][j]的值可以通过比较a[i]和a[j]的值来确定。
-
然后,我们需要计算出每个节点的深度信息。对于每个节点i,我们可以计算出它的深度d[i],即从根节点到该节点的路径上经过的节点数。具体地,我们可以使用一个一维数组depth来存储每个节点的深度信息,初始时depth[0]=0,depth[i]=depth[parent[i]]+1。
-
最后,我们可以使用倍增思想来求解任意区间[l,r]的最小值。具体地,我们可以先找到l和r的最近公共祖先p,然后分别计算出a[l]到a[p]和a[p]到a[r]的最小值,最后将这两个最小值进行比较,即可得到a[l]到a[r]的最小值。
下面是代码示例:
cpp
#include <iostream>
#include <vector>
using namespace std;
const int N = 1e5 + 10;
int n, m;
int a[N];
int pre[N][20], depth[N];
void init() {
for (int i = 1; i <= n; i++) {
pre[i][0] = i;
}
for (int j = 1; j < 20; j++) {
for (int i = 1; i + (1 << j) - 1 <= n; i++) {
if (a[pre[i][j - 1]] < a[pre[i + (1 << (j - 1))][j - 1]]) {
pre[i][j] = pre[i][j - 1];
} else {
pre[i][j] = pre[i + (1 << (j - 1))][j - 1];
}
}
}
for (int i = 2; i <= n; i++) {
depth[i] = depth[pre[i][0]] + 1;
}
}
int lca(int u, int v) {
if (depth[u] > depth[v]) {
swap(u, v);
}
for (int i = 19; i >= 0; i--) {
if (depth[v] - depth[u] >= (1 << i)) {
v = pre[v][i];
}
}
if (u == v) {
return u;
}
for (int i = 19; i >= 0; i--) {
if (pre[u][i] != pre[v][i]) {
u = pre[u][i];
v = pre[v][i];
}
}
return pre[u][0];
}
int query(int l, int r) {
int p = lca(l, r);
int ans = min(a[l], a[r]);
if (l != p) {
ans = min(ans, a[p]);
}
if (r != p) {
ans = min(ans, a[p]);
}
return ans;
}
int main() {
cin >> n >> m;
for (int i = 1; i <= n; i++) {
cin >> a[i];
}
init();
while (m--) {
int l, r;
cin >> l >> r;
cout << query(l, r) << endl;
}
return 0;
}
C++中的std::swap是一个模板函数,用于交换两个变量的值。
std::swap函数是C++标准库中提供的一个泛型函数,它可以交换任何数据类型的两个变量的值。这个函数位于<algorithm>头文件中,使用时需要包含该头文件。std::swap的基本语法如下:
cpp
std::swap(a, b);
其中,a和b是要交换值的两个变量,它们可以是同一类型,也可以是不同的类型,因为std::swap是一个模板函数,它可以根据参数的类型自动选择合适的交换方式。
使用std::swap的好处是代码简洁,不需要手动定义临时变量来存储其中一个变量的值,从而避免了可能的错误和提高了代码的可读性。此外,由于std::swap是内联的,它的执行效率通常也很高。
需要注意的是,如果想要在自定义类型中使用std::swap,可能需要提供对应的赋值运算符和移动构造函数,以便std::swap能够正确地交换对象的状态。