2023下半年总结

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

二进制的转换

正整数转二进制

  1. 直接算

    除 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(二进制)

  2. 利用 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;
}

初级–反转二叉树

将左边的二叉树反转成右边的二叉树
在这里插入图片描述

  1. 明确函数功能
public static class TreeNode
{
    int val;
    TreeNode left;
    TreeNode right;
    TreeNode(int x)
    {
        val = x;
    }
}

public TreeNode invertTree(TreeNode root)
{
}
  1. 查关系

    解题要采用自上而下的思考方式,那我们取前面的 1, 2,3 结点来看,对于根节点 1 来说,假设 2, 3 结点下的节点都已经翻转,那么只要翻转 2, 3 节点即满足需求

在这里插入图片描述

对于 2, 3 结点来说,也是翻转其左右节点即可,依此类推,对每一个根节点,依次翻转其左右节点,所以我们可知问题与子问题的关系是
翻转(根节点) = 翻转(根节点的左节点) + 翻转(根节点的右节点)
invert(root) = invert(root->left) + invert(root->right)
而显然递归的终止条件是当结点为叶子结点时终止(因为叶子节点没有左右结点)

  1. 结束条件
public TreeNode invertTree(TreeNode root) {
    // 叶子结果不能翻转
    if (root == null) {
        return null;
    }
}
  1. 递归条件
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 柱子上去,

期间只有一个原则:

一次只能移到一个盘子且大盘子不能在小盘子上面,求移动的步骤和移动的次数

切忌在每一个子问题上层层展开死抠,这样这就陷入了递归的陷阱,计算机都会栈溢出,何况人脑

在这里插入图片描述

  1. 函数功能
// 将 n 个圆盘从 a 经由 b 移动到 c 上
public void hanoid(int n, char a, char b, char c)
{

}
  1. 查关系-- 自上而下分析
    如果 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`)
  1. 结束条件
public void hanoid(int n, char a, char b, char c)
{
    if (n <= 0)
    {
        return;
    }
}
  1. 递归条件
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 个小时候有多少细胞?

  1. 功能
public int allCells(int n)
{

}
  1. 查关系
    在这里插入图片描述

图中的 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
在这里插入图片描述

在这里插入图片描述

  1. 结束条件
public int aCell(int n)
{
    if(n==1){
        return 1;
    }
}
  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
样例输出
1

121

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)
  1. 解释:复制字符 c(一个无符号字符)到参数 str 所指向的字符串的前 n 个字符

  2. 作用:是在一段内存块中填充某个给定的值,它是对较大的结构体或数组进行清零操作的一种最快方法

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 n2

有一天你在幼儿园的后花园里发现无穷多颗糖果,你打算拿一些糖果回去分给幼儿园的小朋友们。

由于你只是个平平无奇的幼儿园小朋友,所以你的体力有限,至多只能拿 R R R 块糖回去。

但是拿的太少不够分的,所以你至少要拿 L L L 块糖回去。保证 n ≤ L ≤ R n \le L \le R nLR

也就是说,如果你拿了 k k k 块糖,那么你需要保证 L ≤ k ≤ R L \le k \le R LkR

如果你拿了 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;
}
  • 18
    点赞
  • 10
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值