rand() 与 srand()
srand() 用来设置 rand() 产生随机数时的随机数种子。
参数 seed 必须是个整数,如果每次 seed 都设相同值,rand() 所产生的随机数值每次就会一样。
[a,b)的随机整数,使用(rand() % (b-a))+ a;
[a,b]的随机整数,使用(rand() % (b-a+1))+ a;
(a,b]的随机整数,使用(rand() % (b-a))+ a + 1;
通用公式:a + rand() % n;其中的 a 是起始值,n 是整数的范围。
取a到b之间的随机整数,另一种表示:a + (int)b * rand() / (RAND_MAX + 1)
取0~1之间的浮点数,可以使用rand() / double(RAND_MAX)
isupper()判断是否是大写字母
#<ctype.h>头文件
iswupper()判断是否为小写字母
c++里输出指定位数的小数
#include <iomanip> // 控制精度 // IO manipulators 输入输出格式化
按位运算–二进制的加减
bitset
相当于一个二进制的数组,并且可以直接用 01 串赋值
bitset<2>bitset1(12);
//12的二进制为1100(长度为4),但bitset1的size=2,只取后面部分,即00
string s="100101";
bitset<4> bitset2(s);
//s的size=6,而bitset的size=4,只取前面部分,即1001
//char s2[]="11101";
//bitset<4> bitset3(s2);
//与bitset2同理,只取前面部分,即1110
cout << bitset1 << endl; //00
cout << bitset2 << endl; //1001
//cout << bitset3 << endl; //1110
二进制的转换
正整数转二进制
-
直接算
除 2 之后将余数倒序排列, 直到商小于 1
201 / 2 = 100······1
100 / 2 = 50 ······0
50 / 2 = 25 ······0
25 / 2 = 12 ······1
12 / 2 = 6 ······0
6 / 2 = 3 ······0
3 / 2 = 1 ······1
1 / 2 = 0 ······1 (商小于 1,结束计算并将余数倒序排列)
得到:201(十进制) = 11001001(二进制)
-
利用 bitset
int a = 10;
bitset<10> bit(a);
cout << bit << endl;
// 输出:0000001010
小数部分转二进制
1)小数部分乘 2 后将整数部分顺序排列。 如 0.125,整数部分顺序排列得 001
2)前面加 0. 得 0.001
二进制的计算
补码: 可以使符号位和数值位统一处理,同时可以使减法按照加法来处理。
^:按位异或;
&:按位与;
| :按位或
b -> -b : 各位(包括符号位)取反加 1
设a,b为两个二进制数,则a + b = a ^ b+ (a & b) << 1
a^b是不考虑进位时加法结果。当二进制位同时为1时,才有进位,因此 (a&b)<<1是进位产生的值,称为进位补偿。将两者相加便是完整加法结果。
加法
int add(int a, int b)
{
int ans = a;
while(b){
int tmp = a;
a = a ^ b;
b = (tmp & b) << 1;
ans = a;
}
return ans;
}
减法
int subtraction(int a, int b)
{
b = add(~b, 1); // 求b的相反数
return add(a, b);
}
乘法
二进制的乘法同十进制的乘法并无什么不一样,对于 a∗b 每次只需要将 a 左移对应的位,然后同上一次的结果相加即可
当 b 的对应位为 1 的时候,对 a 左移一位相加即可
当 b 的对应位位 0 的时候,对 a 左移一位,但是不相加
注意到我们上面的操作都是不包括符号位的,因此我们单独考虑符号位。
int getSign(int n)
{
unsigned count = 0;
//计算n的位数
do{
++count;
}while(n >> count)
//得到n的最左边的位
return n >> (count-1);
}
int mul(int a, int b){
bool isNegative = false;
if(getSign(a) ^ getSigned(b))
isNegative = true;
if(a < 0) a = add(~a,1);//求出a的绝对值
if(b < 0) b = add(~b,1);//求出b的绝对值
int res = 0;
while(b){ //当b不为0,继续循环
if(b & 1) //当b当前位为1 才需要加a
res = add(res,a);
a = a << 1;
b = b >> 1;
}
if(isNegative)
add(~res,1);
return res;
}
除法
int divide(int a, int b)
{
if(!b)
throw std::runtime_error("Divided can't be zero...");
bool isNegative = false;
bool isNegtive = false;
if(getSign(a) ^ getSign(b))
isNegtive = true;
if(a < 0)
a = add(~a,1); // 求出a的绝对值
if(b < 0)
b = add(~b,1); // 求出b的绝对值
int res = 0;
while(a >= b){
res = add(res,1);
a = subtraction(a,b);
}
if(isNegative)
add(~res,1);
return res;
}
统计字符串中各个不同字符出现的次数
使用 Count 函数
#include <algotirhm>
int main()
{
string temp = "aaabcdaaa!!!";
int num = count(temp.begin(),temp.end(),'a'); //查找指定字符'a'
cout <<"在字符串" << temp << "中," <<"字母a出现的次数是" << num << endl;
return 0 ;
}
使用 Map 库
笨方法(diy)
#include <iostream>
using namespace std;
typedef struct ss
{
char c;
int count;
} sss;
int cmp(const void *a, const void *b)
{
return (*(sss *)a).count < (*(sss *)b).count ? 1 : -1; // 先转化再进行取值运算
}
int FindChar(char *diff, char c)
{
int i;
for (i = 0; i < strlen(diff); i++)
{
if (diff[i] == c)
return i;
}
return -1;
}
void Cal(char *s)
{
cout << s << "中各个字符出现次数排序后" << endl;
int i, j, flag = 1, len_s = strlen(s), count = 0;
sss diff[len_s];
for (i = 0; i < len_s; i++)
{
diff[i].count = 0;
}
char diffC[len_s];
for (i = 0; i < len_s; i++)
{
for (j = 0; j < i; j++)
{
if (s[i] == s[j]) // 找到相同的
{
flag = 0;
break;
}
}
if (flag == 1) // 是第一次出现
{
diff[count].c = s[i];
diffC[count] = s[i];
count++;
}
}
for (i = 0; i < len_s; i++)
diff[FindChar(diffC, s[i])].count++;
qsort(diff, count, sizeof(diff[0]), cmp);
for (i = 0; i < count; i++)
cout << diff[i].c << " "<< diff[i].count <<endl;
cout << endl<< endl;
}
int main()
{
char str1[] = "abcaaabbabc";
Cal(str1);
char str2[] = "AaBbCc";
Cal(str2);
char str3[] = "A";
Cal(str3);
char str4[] = "AAA";
Cal(str4);
return 0;
}
求 π
公式
cmath 头文件里已宏定义
#define M_PI 3.14159265358979323846
#define M_PI_2 1.57079632679489661923
#define M_PI_4 0.785398163397448309616
e 也有
#define M_E 2.71828182845904523536
#define M_LOG2E 1.44269504088896340736
#define M_LOG10E 0.434294481903251827651
递归
入门–跳台阶
飞飞目前位于第 0 级台阶上,他想要前往第 n 级台阶。飞飞每一步可以向上走一级台阶,两级台阶或者四级台阶。
问飞飞恰好走到第 n 级台阶的不同方法数。
思路
定义一个数组 dp,其中 dp[i]表示飞飞到达第 i 级台阶的不同方法数。
飞飞每一步可以向上走一级、两级或四级台阶,因此得到以下状态转移方程:
dp[i] = dp[i-1] + dp[i-2] + dp[i-4]
#include <iostream>
#include <vector>
using namespace std;
int i;
int main()
{
int n;
cin >> n;
vector<long long int> dp(n + 1, 0);
dp[0] = 1; // 已经位于第0级台阶
for (i = 1; i <= n; i++)
{
if (i >= 1)
dp[i] += dp[i - 1];
if (i >= 2)
dp[i] += dp[i - 2];
if (i >= 4)
dp[i] += dp[i - 4];
}
cout << dp[n] << endl;
}
初级–反转二叉树
将左边的二叉树反转成右边的二叉树
- 明确函数功能
public static class TreeNode
{
int val;
TreeNode left;
TreeNode right;
TreeNode(int x)
{
val = x;
}
}
public TreeNode invertTree(TreeNode root)
{
}
- 查关系
解题要采用自上而下的思考方式,那我们取前面的 1, 2,3 结点来看,对于根节点 1 来说,假设 2, 3 结点下的节点都已经翻转,那么只要翻转 2, 3 节点即满足需求
对于 2, 3 结点来说,也是翻转其左右节点即可,依此类推,对每一个根节点,依次翻转其左右节点,所以我们可知问题与子问题的关系是
翻转(根节点) = 翻转(根节点的左节点) + 翻转(根节点的右节点)
invert(root) = invert(root->left) + invert(root->right)
而显然递归的终止条件是当结点为叶子结点时终止(因为叶子节点没有左右结点)
- 结束条件
public TreeNode invertTree(TreeNode root) {
// 叶子结果不能翻转
if (root == null) {
return null;
}
}
- 递归条件
public TreeNode invertTree(TreeNode root) {
// 叶子结果不能翻转
if (root == null) {
return null;
}
// 翻转左节点下的左右节点
TreeNode left = invertTree(root.left);
// 翻转右节点下的左右节点
TreeNode right = invertTree(root.right);
// 左右节点下的二叉树翻转好后,翻转根节点的左右节点
root.right = left;
root.left = right;
return root;
}
中级–汉诺塔
从左到右有 A、B、C 三根柱子,其中 A 柱子上面有从小叠到大的 n 个圆盘,现要求将 A 柱子上的圆盘移到 C 柱子上去,
期间只有一个原则:
一次只能移到一个盘子且大盘子不能在小盘子上面,求移动的步骤和移动的次数
切忌在每一个子问题上层层展开死抠,这样这就陷入了递归的陷阱,计算机都会栈溢出,何况人脑
- 函数功能
// 将 n 个圆盘从 a 经由 b 移动到 c 上
public void hanoid(int n, char a, char b, char c)
{
}
- 查关系-- 自上而下分析
如果 A 柱子上只有两块圆盘
要将 n 个圆盘经由 B 移到 C 柱上去,可以按以下三步来分析
将上面的 n-1 个圆盘看成是一个圆盘,这样分析思路就与上面提到的只有两块圆盘的思路一致了
将上面的 n-1 个圆盘经由 C 移到 B
此时将 A 底下的那块最大的圆盘移到 C
再将 B 上的 n-1 个圆盘经由 A 移到 C 上
得关系式
move(n from A to C) = move(n-1 from A to B) + move(A to C) + move(n-1 from B to C`)
- 结束条件
public void hanoid(int n, char a, char b, char c)
{
if (n <= 0)
{
return;
}
}
- 递归条件
public void move(char a, char b) {
printf("%c->%c\n", a, b);
}
// 将 n 个圆盘从 a 经由 b 移动到 c 上
public void hanoid(int n, char a, char b, char c)
{
if (n <= 0)
{
return;
}
// 将上面的 n-1 个圆盘经由 C 移到 B
hanoid(n-1, a, c, b);
// 此时将 A 底下的那块最大的圆盘移到 C
move(a, c);
// 再将 B 上的 n-1 个圆盘经由A移到 C上
hanoid(n-1, b, a, c);
}
进阶–细胞分裂
细胞分裂 有一个细胞 每一个小时分裂一次,一次分裂一个子细胞,第三个小时后会死亡。那么 n 个小时候有多少细胞?
- 功能
public int allCells(int n)
{
}
- 查关系
图中的 A 代表细胞的初始态, B 代表幼年态(细胞分裂一次), C 代表成熟态(细胞分裂两次),C 再经历一小时后细胞死亡
以 f(n) 代表第 n 小时的细胞分解数
fa(n) 代表第 n 小时处于初始态的细胞数,
fb(n) 代表第 n 小时处于幼年态的细胞数
fc(n) 代表第 n 小时处于成熟态的细胞数
则显然
f(n) = fa(n) + fb(n) + fc(n)
那么 fa(n) 等于多少呢,以 n = 4 (即一个细胞经历完整的生命周期)为例
可以看出 fa(n) = fa(n-1) + fb(n-1) + fc(n-1), 当 n = 1 时,显然 fa(1) = 1
fb(n) 呢,看下图可知 fb(n) = fa(n-1)。 当 n = 1 时 fb(n) = 0
fc(n) 呢,看下图可知 fc(n) = fb(n-1)。当 n = 1,2 时 fc(n) = 0
- 结束条件
public int aCell(int n)
{
if(n==1){
return 1;
}
}
- 递归条件
public int allCells(int n) {
return aCell(n) + bCell(n) + cCell(n);
}
// 第 n 小时 a 状态的细胞数
public int aCell(int n)
{
if(n == 1)
return 1;
return aCell(n-1)+bCell(n-1)+cCell(n-1);
}
// 第 n 小时 b 状态的细胞数
public int bCell(int n)
{
if (n == 1)
return 0;
return aCell(n-1);
}
// 第 n 小时 c 状态的细胞数
public int cCell(int n)
{
if (n == 1 || n == 2)
return 0;
return bCell(n-1);
}
如果一个问题,可以把所有可能的答案穷举出来,并且穷举出来后,发现存在重叠子问题,就可以考虑使用动态规划。
最长递增子序列、最小编辑距离、背包问题、凑零钱问题
判断汉字
C++采用的是 ANSI 编码,是 ASCLL 编码的进阶
字符串可以用 ANSI,但是 char 不行
ASCLL 编码是 DOS 时代的东西了
unsigned char 可以接受 ANSI,光是 char 不行
bool check(unsigned char c)
{
if (c > 0X80)
return true;
return false;
}
控制参与浮点运算的有效位数
转成整数消掉不要的位数再转回来
#include <cmath>
double trim(double d, int acc) {
long tmp = d * pow(10, acc);
return tmp * pow(0.1, acc);
}
int main() {
double pi = 3.141592653;
double trimmed = trim(pi, 3); // 得到3.141
}
numeric_limits
numeric_limits <double>
::max ()
返回编译器允许的 double 型数 最大值。
numeric_limits <int>
::max ()
返回 编译器允许的 int 型数 最大值。
双指针
题目
给定一个长度为 n 的整数数组 height 。有 n 条垂线,第 i 条线的两个端点是 (i, 0) 和 (i, height[i]) 。
找出其中的两条线,使得它们与 x 轴共同构成的容器可以容纳最多的水。
返回容器可以储存的最大水量。
Solve–将短板每次往内移
在每个状态下,无论长板或短板向中间收窄一格,都会导致水槽 底边宽度 ?1-1?1? 变短:
向内 移动短板 ,水槽的短板 min(h[i],h[j]) 可能变大,因此下个水槽的面积 可能增大 。
向内 移动长板 ,水槽的短板 min(h[i],h[j])? 不变或变小,因此下个水槽的面积 一定变小 。
暴力枚举,水槽两板围成面积 S(i,j)的状态总数为 C(n,2)
假设状态 S(i,j)下 h[i]<h[j],在向内移动短板至 S(i+1,j),则相当于消去了 S(i,j?1),S(i,j?2),…,S(i,i+1)状态集合。
而所有消去状态的面积一定都小于当前面积,因为这些状态:
短板高度:相比 S(i,j)相同或更短
底边宽度:相比 S(i,j)更短;
因此,每轮向内移动短板,所有消去的状态都 不会导致面积最大值丢失
int maxArea(vector<int> &height)
{
int i = 0, j = height.size() - 1, area, res = 0;
while (i < j)
{
area = (j - i) * fmin(height[i], height[j]);
res = max(area, res);
if (height[i] < height[j])
i++;
else
j--;
}
return res;
}
高维数组创建方式
#include <iostream>
using namespace std;
int main()
{
// ***** 方式1,栈,整体创建
int a[2][3] = {{1,2,3},{4,5,6}};
for (int i = 0; i < 2; i++)
{
for (int j = 0; j < 3; j++)
a[i][j] = 1;
}
for (int i = 0; i < 2; i++)
{
for (int j = 0; j < 3; j++)
cout << a[i][j] << " ";
cout << endl;
}
// ***** 方式2,栈,分开创建 用的不多
int line1[]={1,0,0};//声明数组,矩阵的第一行
int line2[]={0,1,0};//声明数组,矩阵的第二行
int line3[]={0,0,1};//声明数组,矩阵的第三行
int* p_line[3]; //声明整型指针数组
p_line[0]=line1; //初始化指针数组元素
p_line[1]=line2;
p_line[2]=line3;
for(int i=0;i<3;i++) //对指针数组元素循环
{
for(int j=0;j<3;j++)//对矩阵每一行循环
{
cout<<p_line[i][j]<<" ";
}
cout<<endl;
}
// ***** 方式3,堆,分开创建 重要
int n1, n2;
const int DIM1 = 2;
const int DIM2 = 3;
// 构造数组
int** ppi = new int*[DIM1];
for(n1 = 0; n1 < DIM1; n1++)
{
ppi[n1] = new int[DIM2];
}
// 填充数据
for(n1 = 0; n1 < DIM1; n1++)
{
for(n2 = 0; n2 < DIM2; n2++)
{
ppi[n1][n2] = n1 * 10 + n2;
}
}
// 输出
for(n1 = 0; n1 < DIM1; n1++)
{
for(n2 = 0; n2 < DIM2; n2++)
{
cout << ppi[n1][n2] << " ";
}
cout<<endl;
}
// 释放数组
for(n1 = 0; n1 < DIM1; n1++)
{
delete [] ppi[n1];
}
delete [] ppi;
// *****方式4,堆,整体创建 重要
float (*cp)[3][4];
int i,j,k;
cp = new float[2][3][4];
for (i=0; i<2; i++)
for (j=0; j<3; j++)
for (k=0; k<4; k++)
*(*(*(cp+i)+j)+k)=i*100+j*10+k;
for (i=0; i<2; i++)
{
for (j=0; j<3; j++)
{
for (k=0; k<4; k++)
{
cout<<cp[i][j][k]<<" ";
}
cout<<endl;
}
cout<<endl;
}
return 0;
}
程序运行时间
较简单的方法
#include <ctime>
int main()
{
clock_t begin,end;
begin = clock();
/*中间正常书写代码*/
end = clock();
cout << "runtime: " << double(end - begin) / CLOCKS_PER_SEC; << endl;
}
较为复杂
#include <stdio.h>
#include <chrono>
int main () {
auto begin = std::chrono::high_resolution_clock::now();
// Stop measuring time and calculate the elapsed time
auto end = std::chrono::high_resolution_clock::now();
auto elapsed = std::chrono::duration_cast<std::chrono::nanoseconds>(end - begin);
printf("Time measured: %.3f seconds.\n", elapsed.count() * 1e-9);
return 0;
}
结构体排序–lamda 表达式
[](Student a, Student b){return a.ChineseGrade >= b.ChineseGrade;}
示例
#include <iostream>
#include <algorithm>
using namespace std;
struct Student
{
string name;
int MathGrade;
int ChineseGrade;
};
int main()
{
Student students[5];
students[0] = {"赵", 10, 20};
students[1] = {"钱", 20, 40};
students[2] = {"孙", 50, 30};
students[3] = {"李", 40, 10};
students[4] = {"王", 30, 50};
//语文成绩大的学生排在前面,结构体中的变量ChineseGrade
sort(students, students+5, [](Student a, Student b){return a.ChineseGrade >= b.ChineseGrade;});
for(int i = 0; i < 5; i++)
cout << "name = " << students[i].name << ", mathgrade = " << students[i].MathGrade << ", chinesegrade = " << students[i].ChineseGrade << endl;
return 0;
}
打印数字图形
分成上半部分和下半部分分开打印
题目描述
输入一个整数n(1<=n<=9),打印出指定的数字图形?
注意:每行最后一?数字后没有任何字符?
样例输入
5
样例输出
1121
12321
1234321
1
23454321
1234321
12321
121
1
实现
#include <iostream>
using namespace std;
int i, j;
void PrintTopHalf(int n)
{
for (i = 1; i <= n; i++)
{
for (j = 1; j <= n - i; j++)
cout << " ";
for (j = 1; j <= i; j++)
cout << j;
for (j = i - 1; j >= 1; j--)
cout << j;
cout << endl;
}
}
void PrintBottomHalf(int n)
{
for (i = n - 1; i >= 1; i--)
{
for (j = 1; j <= n - i; j++)
cout << " ";
for (j = 1; j <= i; j++)
cout << j;
for (j = i - 1; j >= 1; j--)
cout << j;
cout << endl;
}
}
int main()
{
int n;
while (cin >> n)
{
PrintTopHalf(n);
PrintBottomHalf(n);
}
return 0;
}
迭代归并排序
void merge_pass(ElementType list[], ElementType sorted[], int N, int length)
{
int i, left, right, subs = 0;
for (i = 0; i < N; i += length * 2)
{
left = i;
right = i + length;
while (left < i + length && right < i + length * 2 && left < N && right < N)
{
if (list[left] < list[right])
sorted[subs++] = list[left++];
else
sorted[subs++] = list[right++];
}
while (left < i + length && left < N)
{
sorted[subs++] = list[left++];
}
while (right < i + length * 2 && right < N)
{
sorted[subs++] = list[right++];
}
}
}
影响速度
cin/cout 解绑
ios::sync_with_stdio(0);
cin.tie(0);
cout.tie(0);
解绑后,不能使用scanf和printf了,快读也是不可以的,因为快读的getchar也是C语言里面的。
endl
endl在使用的时候不仅仅是换行,还会清空缓冲区。速度上可能比"\n"换行慢了10倍。
所以大家完全可以加上
#define endl "\n"
O3/O2优化
代码里有stl的话,一定加上这句话,开启O3优化。
#pragma GCC optimize(2)
#pragma GCC optimize(3)
#pragma GCC optimize("Ofast")
#pragma GCC optimize("inline")
#pragma GCC optimize("-fgcse")
#pragma GCC optimize("-fgcse-lm")
#pragma GCC optimize("-fipa-sra")
#pragma GCC optimize("-ftree-pre")
#pragma GCC optimize("-ftree-vrp")
#pragma GCC optimize("-fpeephole2")
#pragma GCC optimize("-ffast-math")
#pragma GCC optimize("-fsched-spec")
#pragma GCC optimize("unroll-loops")
#pragma GCC optimize("-falign-jumps")
#pragma GCC optimize("-falign-loops")
#pragma GCC optimize("-falign-labels")
#pragma GCC optimize("-fdevirtualize")
#pragma GCC optimize("-fcaller-saves")
#pragma GCC optimize("-fcrossjumping")
#pragma GCC optimize("-fthread-jumps")
#pragma GCC optimize("-funroll-loops")
#pragma GCC optimize("-fwhole-program")
#pragma GCC optimize("-freorder-blocks")
#pragma GCC optimize("-fschedule-insns")
#pragma GCC optimize("inline-functions")
#pragma GCC optimize("-ftree-tail-merge")
#pragma GCC optimize("-fschedule-insns2")
#pragma GCC optimize("-fstrict-aliasing")
#pragma GCC optimize("-fstrict-overflow")
#pragma GCC optimize("-falign-functions")
#pragma GCC optimize("-fcse-skip-blocks")
#pragma GCC optimize("-fcse-follow-jumps")
#pragma GCC optimize("-fsched-interblock")
#pragma GCC optimize("-fpartial-inlining")
#pragma GCC optimize("no-stack-protector")
#pragma GCC optimize("-freorder-functions")
#pragma GCC optimize("-findirect-inlining")
#pragma GCC optimize("-fhoist-adjacent-loads")
#pragma GCC optimize("-frerun-cse-after-loop")
#pragma GCC optimize("inline-small-functions")
#pragma GCC optimize("-finline-small-functions")
#pragma GCC optimize("-ftree-switch-conversion")
#pragma GCC optimize("-foptimize-sibling-calls")
#pragma GCC optimize("-fexpensive-optimizations")
#pragma GCC optimize("-funsafe-loop-optimizations")
#pragma GCC optimize("inline-functions-called-once")
#pragma GCC optimize("-fdelete-null-pointer-checks")
unordered_map / set
如何你只是需要一个映射关系的话,不需要有序的话,可以尝试unordered_map。看了一些测试数据,基本上快了一倍吧。
有人提醒到用hash表的话,可能会在cf上被hack,通过构造大量的hash值相同的数,使得hash表退化成一条链。
find的复杂度变成
所以我们可以使用自定义的hash函数。
struct custom_hash {
static uint64_t splitmix64(uint64_t x) {
x += 0x9e3779b97f4a7c15;
x = (x ^ (x >> 30)) * 0xbf58476d1ce4e5b9;
x = (x ^ (x >> 27)) * 0x94d049bb133111eb;
return x ^ (x >> 31);
}
size_t operator()(uint64_t x) const {
static const uint64_t FIXED_RANDOM = chrono::steady_clock::now().time_since_epoch().count();
return splitmix64(x + FIXED_RANDOM);
}
};
unordered_map<int, int, custom_hash> safe_map;
int 和 long long 的速度差不多的
long long 在取模除法的时候就会明显慢于int
const int mod
不加const的话,取模会慢很多的。
但是开了O3后,编译器会帮你手动优化的。
结构体线段树
有两种写线段树的方法,一种是数组,另一种是包结构体里面。
struct node {
int sum, lz;
}tree[MAXN << 2];
int sum[MAXN << 2];
int lz[MAXN << 2];
理论上第一个cache命中率高一些,但快不了多少。
不过用结构体写可以用代码补全(确信
按位运算
if (b & 1) 判断奇数
判断是不是偶数 跟 if(n%2==1)这个是一样的
n&1,将 n 变成二进制 跟 二进制的 1 00000000 00000001 按位做与操作。
这时,只要 n 的最右边一位是 1,结果就不是 0,为 true
a 右移一位 == a 除以 2;左移一位 == a 乘以 2
int a = 2;
a >> 1; ---> 1
a << 1; ---> 4
~a + 1 正数变成负数,负数变成正数
int reversal(int a)
{
return ~a + 1;
}
memset给char以外的数组初始化为0,1
void *memset(void *str, int c, size_t n)
-
解释:复制字符 c(一个无符号字符)到参数 str 所指向的字符串的前 n 个字符
-
作用:是在一段内存块中填充某个给定的值,它是对较大的结构体或数组进行清零操作的一种最快方法
memset赋值的时候是按字节赋值,是将参数化成二进制之后填入一个字节。
通过memset(a,100,sizeof a)给int类型的数组赋值,你给第一个字节的是一百,转成二进制就是0110 0100,而int有四个字节,也就是说,一个int被赋值为
0110 0100,0110 0100,0110 0100,0110 0100,对应的十进制是1684300900,根本不是你想要赋的值100,这也就解释了为什么数组中的元素的值都为1684300900。
可以采用vector对数组初始化
vector<int>dp(n + 1, 1);
为地址str开始的n个字节赋值c,注意:是逐个字节赋值,str开始的n个字节中的每个字节都赋值为c。
(1) 若str指向char型地址,value可为任意字符值;
(2) 若str指向非char型,如int型地址,要想赋值正确,value的值只能是-1或0,因为-1和0转化成二进制后每一位都是一样的,设int型占4个字节,则-1=0XFFFFFFFF, 0=0X00000000。
分糖果
题目描述
红太阳幼儿园有 n n n 个小朋友,你是其中之一。保证 n ≥ 2 n \ge 2 n≥2。
有一天你在幼儿园的后花园里发现无穷多颗糖果,你打算拿一些糖果回去分给幼儿园的小朋友们。
由于你只是个平平无奇的幼儿园小朋友,所以你的体力有限,至多只能拿 R R R 块糖回去。
但是拿的太少不够分的,所以你至少要拿 L L L 块糖回去。保证 n ≤ L ≤ R n \le L \le R n≤L≤R。
也就是说,如果你拿了 k k k 块糖,那么你需要保证 L ≤ k ≤ R L \le k \le R L≤k≤R。
如果你拿了 k k k 块糖,你将把这 k k k 块糖放到篮子里,并要求大家按照如下方案分糖果:只要篮子里有不少于 n n n 块糖果,幼儿园的所有 n n n 个小朋友(包括你自己)都从篮子中拿走恰好一块糖,直到篮子里的糖数量少于 n n n 块。此时篮子里剩余的糖果均归你所有——这些糖果是作为你搬糖果的奖励。
作为幼儿园高质量小朋友,你希望让作为你搬糖果的奖励的糖果数量(而不是你最后获得的总糖果数量!)尽可能多;因此你需要写一个程序,依次输入 n , L , R n, L, R n,L,R,并输出你最多能获得多少作为你搬糖果的奖励的糖果数量。
friend ostream &operator<<(ostream &os, const myString &str)
是一个友元函数的声明,它的作用是重载输出流运算符<<,以便能够直接将myString对象输出到输出流中。
友元函数是在类外部声明的,但可以访问类的私有成员。在这种情况下,operator<<函数是myString类的友元函数,因此它可以访问myString类的私有成员变量p和len。
通过重载<<运算符,我们可以使用类似于cout << str的语法来将myString对象str输出到标准输出流。在operator<<函数的实现中,我们可以使用os输出流对象来输出myString对象的内容。
在你提供的代码中,operator<<函数的实现如下:
ostream& operator<<(ostream& os, const myString& str)
{
os << str.p;
return os;
}
在这个实现中,我们将myString对象的p成员变量直接输出到输出流中,以便将myString对象的内容打印到屏幕上。
使用重载的<<运算符,你可以像这样输出myString对象:
myString str("Hello");
cout << str << endl;
// 输出Hello。
lower_bound()
/ upper_bound()
在已升序排序的元素中,应用二分查找检索指定元素,返回对应元素迭代器位置。找不到则返回尾迭代器。
lower_bound()
: 寻找 ≥ x \geq x ≥x 的第一个元素的位置upper_bound()
: 寻找 > x >x >x 的第一个元素的位置
怎么找 ≤ x \leq x ≤x / < x < x <x 的第一个元素呢?
- > x >x >x 的第一个元素的前一个元素(如果有)便是 ≤ x \leq x ≤x 的第一个元素
- ≥ x \geq x ≥x 的第一个元素的前一个元素(如果有)便是 < x <x <x 的第一个元素
返回的是迭代器,如何转成下标索引呢?减去头迭代器即可。
用法示例
template< class ForwardIt, class T >
ForwardIt lower_bound( ForwardIt first, ForwardIt last, const T& value );
vector<int> arr{0, 1, 1, 1, 8, 9, 9};
vector<int>::iterator it = lower_bound(arr.begin(), arr.end(), 7);
int idx = it - arr.begin();
// idx = 4
我们通常写成一行:
vector<int> arr{0, 1, 1, 1, 8, 9, 9};
idx = lower_bound(arr.begin(), arr.end(), 7) - arr.begin(); // 4
idx = lower_bound(arr.begin(), arr.end(), 8) - arr.begin(); // 4
idx = upper_bound(arr.begin(), arr.end(), 7) - arr.begin(); // 4
idx = upper_bound(arr.begin(), arr.end(), 8) - arr.begin(); // 5
Code
//找大于等于某数的第一个数,查找的数组必须有序
int n = 7;//7个数
int a[] = { 2,4,6,7,9,12,111 };//范围:0 ~ 6
int t = lower_bound(a, a + n, 8) - a;//数组中大于等于8的第一个数
if (t != n)//找不到会返回边界,边界是 7
cout << a[t] << endl;
int b[] = { 0,2,4,6,7,9,12,111 };//范围:1 ~ 7
t = lower_bound(b + 1, b + n + 1, 8) - b;
if (t != n + 1)//找不到会返回边界,边界是 8
cout << b[t] << endl;
reverse()
反转一个可迭代对象的元素顺序
用法示例
template< class BidirIt >
void reverse( BidirIt first, BidirIt last );
vector<int> arr(10);
iota(arr.begin(), arr.end(), 1);
// 1, 2, 3, 4, 5, 6, 7, 8, 9, 10
reverse(arr.begin(), arr.end());
// 10, 9, 8, 7, 6, 5, 4, 3, 2, 1
3.6 unique()
消除数组的重复相邻元素,数组长度不变,但是有效数据缩短,返回的是有效数据位置的结尾迭代器。
例如: [ 1 , 1 , 4 , 5 , 1 , 4 ] → [ 1 , 4 , 5 , 1 , 4 , ? ‾ ] [1,1,4,5,1,4]\to[1,4,5,1,4,\underline?] [1,1,4,5,1,4]→[1,4,5,1,4,?],下划线位置为返回的迭代器指向。
template< class ForwardIt >
ForwardIt unique( ForwardIt first, ForwardIt last );
用法示例
单独使用 unique 并不能达成去重效果,因为它只消除相邻的重复元素。但是如果序列有序,那么它就能去重了。
但是它去重后,序列尾部会产生一些无效数据: [ 1 , 1 , 2 , 4 , 4 , 4 , 5 ] → [ 1 , 2 , 4 , 5 , ? ‾ , ? , ? ] [1,1,2,4,4,4,5]\to[1,2,4,5,\underline?,?,?] [1,1,2,4,4,4,5]→[1,2,4,5,?,?,?],为了删掉这些无效数据,我们需要结合 erase.
最终,给 vector 去重的写法便是:
vector<int> arr{1, 2, 1, 4, 5, 4, 4};
sort(arr.begin(), arr.end());
arr.erase(unique(arr.begin(), arr.end()), arr.end());
Code
vector<int> vec = { 1,3,4,5,1,1,9 };
sort(vec.begin(), vec.end());
//unique本身的功能是将排序后的数组内的所有重复元素在 O(n) 时间内堆积到数组末端
//同时它会返回一个指针/下标(区别于你传入的是容器还是数组) —— 堆积的第一个重复元素的位置
vec.erase(unique(vec.begin(), vec.end()), vec.end());
//我们再利用vector的区间删除功能就能完成去重的过程
for (auto j : vec)cout << j << ' ';
int a[10] = { 1,2,3,8,7,5,3,1,2,4 };
int n = 10;
sort(a, a + n);//排序
forr(i, 0, n - 1)cout << a[i] << ' ';
cout << endl;
n = unique(a, a + n) - a;
//n 之后的元素似乎会有变动,但不影响去重本身的正确性
forr(i, 0, n - 1)cout << a[i] << ' ';
C++
动态创建 Point
#include <iostream>
using namespace std;
class Point
{
public:
Point()
{
X = Y = 0;
cout << "Default Constructor called.\n";
}
Point(int xx, int yy)
{
X = xx;
Y = yy;
cout << "Constructor called.\n";
}
~Point() { cout << "Destructor called.\n"; }
int GetX() { return X; }
int GetY() { return Y; }
void Move(int x, int y)
{
X = x;
Y = y;
}
private:
int X, Y;
};
int main()
{
cout << "Step One:" << endl;
Point\*Ptr1 = new Point;
delete Ptr1;
cout << "Step Two:" << endl;
Ptr1 = new Point(1, 2);
delete Ptr1;
return 0;
}
动态创建 Point 数组
#include <iostream>
using namespace std;
class Point
{
public:
Point()
{
X = Y = 0;
cout << "Default Constructor called.\n";
}
Point(int xx, int yy)
{
X = xx;
Y = yy;
cout << "Constructor called.\n";
}
~Point() { cout << "Destructor called.\n"; }
int GetX() { return X; }
int GetY() { return Y; }
void Move(int x, int y)
{
X = x;
Y = y;
}
private:
int X, Y;
};
int main()
{
Point *Ptr = new Point[2]; // 创建对象数组
Ptr[0].Move(5, 10); // 通过指针访问数组元素的成员
Ptr[1].Move(15, 20); // 通过指针访问数组元素的成员
cout << "Deleting..." << endl;
delete[] Ptr; // 删除整个对象数组
return 0;
}
将 Point 数组写完整
1.不显示初始化对象数组
#include <iostream>
using namespace std;
class Point
{
public:
Point(int _x = 0, int _y = 0)
{
x = _x;
y = _y;
}
int GetX() { return x; }
int GetY() { return y; }
private:
int x, y;
};
int main()
{
Point p[2]; // 必须得有不带参数的构造,或有默认形参值的构造
cout << p[0].GetX() << " " << p[0].GetY() << endl;
cout << p[1].GetX() << " " << p[1].GetY() << endl;
}
2.显示初始化对象数组方式 1
#include <iostream>
using namespace std;
class Point
{
public:
Point(int _x = 0, int _y = 0)
{
x = _x;
y = _y;
}
int GetX() { return x; }
int GetY() { return y; }
private:
int x, y;
};
int main()
{
Point p[2] = {Point(1, 2), Point(3, 4)};
cout << p[0].GetX() << " " << p[0].GetY() << endl;
cout << p[1].GetX() << " " << p[1].GetY() << endl;
}
3.显示初始化对象数组方式 2
#include <iostream>
using namespace std;
class Point
{
public:
Point(int _x = 0, int _y = 0)
{
x = _x;
y = _y;
}
Point(Point &p);
int GetX() { return x; }
int GetY() { return y; }
void AddX() { ++x; }
void AddY() { ++y; }
private:
int x, y;
};
Point::Point(Point &p)
{
x = p.x;
y = p.y;
cout << "Point 拷贝构造函数被调用" << endl;
}
int main()
{
Point p1(1, 2), p2(4, 5);
Point p[2] = {p1, p2}; // 会调用拷贝构造函数,放进去的是拷贝,验证它
cout << p[0].GetX() << " " << p[0].GetY() << endl;
cout << p[1].GetX() << " " << p[1].GetY() << endl;
}
#include <iostream>
using namespace std;
class Point
{
public:
Point(int _x = 0, int _y = 0)
{
x = _x;
y = _y;
}
Point(Point &p);
int GetX() { return x; }
int GetY() { return y; }
void AddX() { ++x; }
void AddY() { ++y; }
private:
int x, y;
};
Point::Point(Point &p)
{
x = p.x;
y = p.y;
cout << "Point 拷贝构造函数被调用" << endl;
}
main()
{
Point p1(1, 2), p2(4, 5);
Point p[2] = {p1, p2};
p1.AddX();
p1.AddY();
p2.AddX();
p2.AddY();
cout << p1.GetX() << " " << p1.GetY() << endl;
cout << p2.GetX() << " " << p2.GetY() << endl;
cout << p[0].GetX() << " " << p[0].GetY() << endl;
cout << p[1].GetX() << " " << p[1].GetY() << endl;
}
例题
1
有以下基本书,请编写书本类,通过对象数组进行存储。
谭浩强 C 语言程序设计 58.00 清华大学出版社
严伟明 数据结构 68.50 清华大学出版社
张伟 图像处理 128.00 人民出版社
#include <iostream>
#include <cstring>
using namespace std;
class Book
{
public:
Book();
Book(char *, double, char *, char *);
Book(Book &);
~Book();
void PrintInfo();
void ChangeInfo(char *, double, char *, char *);
private:
char name[20];
double price;
char press[20];
char author[10];
};
Book::Book()
{
strcpy(name, "aaa");
price = 0.0;
strcpy(press, "aaa");
strcpy(author, "aaa");
}
Book::Book(char *n, double pri, char *pre, char\*a)
{
strcpy(name, n);
price = pri;
strcpy(press, pre);
strcpy(author, a);
}
Book::Book(Book &b)
{
strcpy(name, b.name);
price = b.price;
strcpy(press, b.press);
strcpy(author, b.author);
}
Book::~Book()
{
}
void Book::PrintInfo()
{
cout << "The info of the book:" << name << " " << price << " " << press << " " << author << endl;
}
void Book::ChangeInfo(char *n, double pri, char *pre, char\*a)
{
strcpy(name, n);
price = pri;
strcpy(press, pre);
strcpy(author, a);
}
int main()
{
// Book b[3]={Book("C 语言程序设计",26.8,"清华大学出版社","谭浩强"),Book("图像处理",31.6,"科学出版社","李明"),Book("数据结构",68.50,"清华大学出版社","严伟明")};
Book b1("C 语言程序设计", 26.8, "清华大学出版社", "谭浩强");
Book b2("图像处理", 31.6, "科学出版社", "李明");
Book b3("数据结构", 68.50, "清华大学出版社", "严伟明");
Book b[3] = {b1, b2, b3};
for (int i = 0; i < 3; i++)
{
b[i].PrintInfo();
}
return 0;
}
2
编写一个程序,输入 N 个学生数据,包括学号、姓名、成绩,要求输出这些学生数据并计算平均分。(通过对象数组来测试)
#include <iostream>
#include <cstring>
using namespace std;
const int N = 3;
class Stud
{
public:
Stud()
{
no = 0;
deg = 0;
strcpy(name, "abc");
}
void setdata(int n, char na[], int d)
{
no = n;
deg = d;
strcpy(name, na);
sum += d;
num++;
}
static double avg()
{
return sum / num;
}
void disp()
{
cout << no << " " << name << " " << deg << endl;
}
private:
int no;
char name[10];
int deg;
static int num;
static int sum;
};
int Stud::sum = 0;
int Stud::num = 0;
int main()
{
Stud s[N];
int i, n, d;
char na[10];
for (i = 0; i < N; i++)
{
cout << "输出数据:" << endl;
cout << "学号 姓名 成绩" << endl;
s[i].disp();
}
for (i = 0; i < N; i++)
{
cout << "输入学号 姓名 成绩:" << endl;
cin >> n >> na >> d;
s[i].setdata(n, na, d);
}
for (i = 0; i < N; i++)
{
cout << "输出数据:" << endl;
cout << "学号 姓名 成绩" << endl;
s[i].disp();
}
cout << "平均分=" << Stud::avg() << endl;
return 0;
}
4
编写一个程序,输入 N 个学生数据,包括学号、姓名、成绩,要求输出这些学生数据并计算平均分。(通过对象数组和对象指针来测试)
#include <iostream>
#include <cstring>
using namespace std;
const int N = 3;
class Stud
{
public:
Stud()
{
no = 0;
deg = 0;
strcpy(name, "abc");
}
void setdata(int n, char na[], int d)
{
no = n;
deg = d;
strcpy(name, na);
sum += d;
num++;
}
static double avg()
{
return sum / num;
}
void disp()
{
cout << no << " " << name << " " << deg << endl;
}
private:
int no;
char name[10];
int deg;
static int num;
static int sum;
};
int Stud::sum = 0;
int Stud::num = 0;
int main()
{
Stud s[N];
Stud *p;
int i, n, d;
char na[10];
for (i = 0; i < N; i++)
{
cout << "输出数据:" << endl;
cout << "学号 姓名 成绩" << endl;
p = &s[i];
p->disp();
}
for (i = 0; i < N; i++)
{
cout << "输入学号 姓名 成绩:" << endl;
cin >> n >> na >> d;
p = &s[i];
p->setdata(n, na, d);
}
for (i = 0; i < N; i++)
{
cout << "输出数据:" << endl;
cout << "学号 姓名 成绩" << endl;
p = &s[i];
p->disp();
}
cout << "平均分=" << Stud::avg() << endl;
return 0;
}
5
编写一个程序,输入 N 个学生数据,包括学号、姓名、成绩,要求输出这些学生数据并计算平均分。(通过动态内存分配对象数组测试)
#include <iostream>
#include <cstring>
using namespace std;
const int N = 3;
class Stud
{
public:
Stud()
{
no = 0;
deg = 0;
strcpy(name, "abc");
}
void setdata(int n, char na[], int d)
{
no = n;
deg = d;
strcpy(name, na);
sum += d;
num++;
}
static double avg()
{
return sum / num;
}
void disp()
{
cout << no << " " << name << " " << deg << endl;
}
private:
int no;
char name[10];
int deg;
static int num;
static int sum;
};
int Stud::sum = 0;
int Stud::num = 0;
int main()
{
Stud *p = new Stud[N];
Stud *p2 = 0;
int i, n, d;
char na[10];
for (i = 0; i < N; i++)
{
cout << "输出数据:" << endl;
cout << "学号 姓名 成绩" << endl;
p2 = &p[i];
p2->disp();
// p[i].disp();
}
for (i = 0; i < N; i++)
{
cout << "输入学号 姓名 成绩:" << endl;
cin >> n >> na >> d;
p2 = &p[i];
p2->setdata(n, na, d);
// p[i].setdata(n,na,d);
}
for (i = 0; i < N; i++)
{
cout << "输出数据:" << endl;
cout << "学号 姓名 成绩" << endl;
p2 = &p[i];
p2->disp();
// p[i].disp();
}
cout << "平均分=" << Stud::avg() << endl;
delete[] p;
return 0;
}