C++的代码技巧和习惯


1.需要一个数组或者字符串的目前的元素和前一个元素做相关运算时:
直接遍历这个序列,当 i 等于0时不满足条件,只有i 等于 1时才会执行if语句

for(int i = 0; i < len; ++i) {
	if( i && 相关的运算) ...
}

2.与前一个元素做相关运算
我偏向定义一个变量pre代表前一个元素,然后从第二个位置开始遍历
感觉这样的代码确实有点多了,而且每次要更新pre的值

int pre = num[1];
for(int i=2;i<=n;i++)
{
	相关的运算
	pre = num[i];
}

3.存ASCII是否访问过,直接vis[200],开足够大的空间,ASCII本身也是一个数,直接用字符操作就行

vis[s[i]] ++;

4.判断一个序列是否是等差数列
遍历这个序列,将每个差分插入到一个集合中,如果最后集合的大小等于1,则为等差数列,还可以判断不同差分值的个数。
还可以通过set的方法访问首值和尾值

set<int>s;
int x = *s.begin();
int y = *s.rbegin();

5.在局部区域定义一个初始值为0的数组

int a[N]{};

可以发现最后a数组中的值都为0,和全局域定义的数组效果几乎一样。

vector数组第二维还可以赋值,将一个一维动态数组赋给一个二维数组的第二维

有一个定义数组的小技巧

//定义一个二维数组,第一维长度为200,第二维长度为0
 vector<vector<int>> bk(200,vector<int>(0));
 
 //m*n的二维vector,所有元素为0
 vector<vector <int> > num(m ,vector<int>(n,0));

6.枚举二进制时的思路:一般数据范围比较小,需要枚举02cnt10\backsim2 ^{cnt}-1每个数,然后再对每个二进制位进行判断

for(int i=1;i<(1<<cnt);i++)//对每个数进行枚举
{
   for(int j=0;j<cnt;j++)//对每个二进制位数进行判断
   {
     if(i&(1<<j))// i 代表这个数  1<<j是每个二进制位为1循环一遍,进行与运算,判断i的二进制位
     {

     }
   }

7.定义vector用简单代码的输入,从0位置开始

vector<int> a(n);
for(auto &x : a){cin >> x;}

8.获得vector中的最大值(最小值)
max_element(v.begin(),v.end()) 返回的是最大值所在的迭代器,即位置
min_element(v.begin(),v.end()) 返回的是最大值所在的迭代器,即位置

vector<int >v;

int mx = *max_element(v.begin(),v.end());
int mn = *min_element(v.begin(),v.end());

9.c++结构体也可以写成员函数,通过打点来访问

struct node{
    int cnt[N], ans = 0;
    int add(int x)
    {		
    	......
    }
}a[2];

10.卡空格写法

int f=1;
for(int i=1;i<=n;i++)
{
	if(f)f=0; //如果为第一个元素,不输出空格,f置为0,接下来输出空格
	else printf(" ");
	
}

11.求区间交集
有两个区间[a,b] [c,d],求区间交集长度

if(b<c||a>d) cout<<"无交集\n"; 
else ll len = min(b,d)-max(a,c);

12.结构体也可以进行强制转化,真的是没想到

struct node
{
int x,y,z;
}a[105];//如果要添加构造函数的话,创建结构体数组就会出错,因为没有传入参数,无法调用构造函数
 a[1] = (node){1,2,3};//将1,2,3强行转换为结构体类型,顺便还能赋值

13.前缀和还可以这样来求

for(int i=1;i<=n;i++)
{
	a[i] += a[i-1];
}//直接用一个数组替代了两个数组

14.结论一条:
偏序定理:序列中 递减子序列的个数等于序列的最长上升子序列的长度

  • 用于求最长上升子序列和最长非上升子序列

15.输入的结束标志
当输入的数都是0时,便结束输入

while(cin>>x>>y>>z,x||y||z)
{

}

16.等于运算符(==)的优先级高于位运算符的优先级

乘除余 > 加减 > 左右移 > 关系运算符(>,<,>=,<=) > 与 异或 或 非 > 逻辑与或(&& || )> 赋值运算符 > (/=, %= ,*=)


17.找树的直径

  • 任取一点作为起点,找到距该点最远的一个点u,(dfs,bfs)
  • 再找距离u最远的一个点v,(dfs,bfs)
  • 那么u和v之间的路径就是树的直径

18.输入输出同步解除和puts()不能同时使用


19.移位运算时一定要注意数据范围,要不然很容易吃这方面的亏
结果的数据类型取决于前面的数字

int x = 1ll<<i;
//一定要注意前面的数字1数据为long long类型

20.数组切段技巧,将相同元素的数组分段

for(int l=1,r=1;l<=n;l=r+1,r=l)
{
	while(r < n && a[l]==a[r+1]) r++;
	printf("%d %d",l,r);
}

21.lamda表达式排序操作
例对数组进行从大到小的排序

sort(a + 1, a + 1 + n, [](const int&A, const int&B) {
     return A > B;
});

或者

sort(a+1,a+1+n,greater<int>());

22.格式控制技巧

//res数组是答案,进行输出
for(int i=1;i<=n;i++)
	cout<<res[i]<<" \n"[i==n];

" \n"[i==n]是格式控制,前面当作string字符串,后面是下标的表达式,当i!=n时,下标取0,输出空格,当i==n下标取1,输出换行。


23.Mex操作
数组a的前缀取Mex操作:

vis[i]:代表数组中数字i是否出现,需要从前往后进行维护,不能一下预处理完
tmp:代表从第一个元素开始到当前的元素,mex值为tmp

int tmp = 0;
for(int i=1;i<=n;i++)
{
	vis[a[i]] = true;//维护vis
	while(vis[tmp]) tmp++;
}

24.浮点比较方法

const double eps = 1e-6;
bool cmp(double a,double b)
{
    if(fabs(a-b)<eps) return true; //相等
    return false;//不相等
}

25.取反操作:对正整数的每一位取反,除去符号位和前面的无效位。定义c(x)为取反操作
c(000101)=000010,即只看后面的三位
n为2的幂次方,则c(x)=x(n1),n1=111...1112c(x) = x \oplus (n-1),n-1=111...111_2


26.__builtin_popcount()c++内置函数:统计一个数x二进制中有多少个1


27.map查找元素是否存在时,可以使用
1️⃣mp.find() 2️⃣ mp.count() 3️⃣ mp[key]
但是第三种情况,如果不存在对应的key时,会自动创建一个键值对(产生一个额外的键值对空间)
所以为了不增加额外的空间负担,最好使用前两种方法


28.让n变成小于等于n且是m的倍数(把n削减成最大的m的倍数)

n = n / m * m;

29.位运算枚举子集
一个数的初始值的二进制代表集合中的元素取和不取,1代表取,0代表不取。
s0s0代表初始集合,i代表每次产生的新的子集
集合统一使用一个数进行表示

for(int i = s0; i ; i = (i - 1) & s0)//递减和原集合进行位运算
{
	
}

30.对一个数组排序的最小操作数 = n - 环数

详情看:https://blog.csdn.net/yunxiaoqinghe/article/details/113153795

31.有m个区间,区间可能会有重合相交,每个区间都会对区间内的数字产生影响(可认为是区间加和),如果要单独剖出所有单个点不受影响的情况,可以进行如下想法:
区间存储可用以下数组:

vector<vector<int>> L(n + 1), R(n + 1);
//每次的读入
cin >> seg[i].first >> seg[i].second;
L[seg[i].first].push_back(seg[i].second);
R[seg[i].second].push_back(seg[i].first);

L[i]代表以左端点i为起始点的右端点数组
R[i]代表以右端点i为终端点的右端点数组
先将所有区间加和操作执行完,然后进行循环(类似扫描线),遇到区间开头就消除以此为左端点区间的影响,离开区间结尾就加上以此为右端点区间的影响

for(int i = 1; i <= n; i++)
{
    for(int j = 0; j < int(L[i].size()); j++)
        modify(1, 1, n, i, L[i][j], 1);
    if(tr[1].mx - tr[1].mn > ans)
    {
    //区间不包括端点i的处理
        pos = i;
        ans = tr[1].mx - tr[1].mn;
    }
    for(int j = 0; j < int(R[i].size()); j++)
        modify(1, 1, n, R[i][j], i, -1);
}

32.最长路采用的是double乘法计算最长路(即边权相乘),将边权log一下,转化为边权相加,防止爆精度。即边权求log(w)log(w)当做新的边权,然后跑最短路或者最长路时,比较的是dis + w

33.sqrt函数可能会有精度误差,里面是double的类型,如果传入long long类型,强转为double会有误差,建议sqrt加强一下。

ll t = sqrt(x);
while(t * t > x) t--;
while((t + 1) * (t + 1) <= x) t++;

或者使用sqrtl,参数类型是long double

ll t = sqrtl(x);
while(t * t > x) t--;
while((t + 1) * (t + 1) <= x) t++;

或者在里面直接转换为long double类型。

ll t = sqrt((long double) x);

34.统计一个数的二进制下的位数

int cnt = 0;
ll x = 114514232;
while (x >> cnt) ++cnt;
  1. 使用auto定义lambda表达式递归函数
auto dfs = [&](auto&& dfs, int u, int p) -> void {
	for (auto v: g[u]) {
		if (v == p) continue;
		dfs(dfs, v, u);
	}
}; 

持续更新中… …


   转载规则


《C++的代码技巧和习惯》 行码棋 采用 知识共享署名 4.0 国际许可协议 进行许可。
 上一篇
【Mit6.s081】课程记录 【Mit6.s081】课程记录
实验代码记录:https://github.com/anda522/XV6-LAB/tree/main 课程教材翻译:https://xv6.dgs.zone/ ,对理解感觉很重要 xv6系统为 RISC-V 知识记录 编译操作
2023-11-14 2024-06-06
下一篇 
个人算法题单整理 个人算法题单整理
记录个人刷过的算法经典题目(好题),以备复习回顾。 一级目录参考 OI Wiki 进行设置。 语言基础 1 STL 1.1 set 知识点 难度 来源 multiset插入删除,异或运算性质:相邻元素值异或小于非相邻元素
2023-11-06 2024-05-13
  目录