算阶1例题的博客链接
0x01 位运算
与 | & | and | 全1则1 |
或 | \ | or | 有1则1 |
非 | ~ | not | 是0则1 |
异或 | ^ | xor | 不同则1 |
左移 | << | 相当于 *2 | |
右移 | >> | 相当于 /2 |
因为
∣
|
∣ 在表格中打不出,用 \ 代替
补码
32位二进制,最高位 0表示正 1表示负 | |
---|---|
原码 | 首位符号位,其余真值 |
反码正数 | 与原码一样 |
反码负数 | 符号位和原码一样,其他位相反 |
补码正数 | 与原码一样 |
补码负数 | 等于反码加一 |
最高位不进位,超过取模
2
32
2^{32}
232
十六进制
0x声明了进制
0
x
7
F
F
F
F
F
F
F
0x7FFFFFFF
0x7FFFFFFF
i
n
t
int
int能表示的最大正整数
m
e
m
s
e
t
memset
memset只能赋值“每8位字节都相同的
i
n
t
int
int”,赋值正无穷用
0
x
3
f
0x3f
0x3f
移位运算
a
<
<
b
a<<b
a<<b 表示a*
2
b
2^{b}
2b
a
>
>
b
a>>b
a>>b 表示
a
2
b
\frac{a}{2^{b}}
2ba (算术右移)
(c++中/表示向0取整,例如 -3/2=-1
)
算术右移:在二进制补码表示下,把数字向右移动,最高位以 符号位 填充,低位越界舍弃
逻辑右移:在二进制补码表示下,把数字向右移动,最高位以 0 填充,低位越界舍弃
快速幂
计算 a b a^{b} ab
int power(int a, int b){
int ans = 1;
while(b){
if(b & 1) ans = ans * a;//如果b是奇数
a = a*a;//改变a的值
//如果a*a很大,爆了longlong怎么办,这时候我们就需要引进快速乘了!
b >>= 1;//b除以2
}
return ans;
}
快速乘
计算 a ∗ b a*b a∗b
int power(int a, int b){
int ans = 0;
while(b){
if(b & 1) ans = ans + a;//如果b是奇数
a *= 2;//改变a的值
b >>= 1;//b除以2
}
return ans;
}
两个数值执行算术运算时,以参加运算的最高数值类型(a或b)为基准,与保存结果的变量类型(d)无关
例如
int a;
int b;
//主程序开始
long long d = a*b;//如果a*b的结果超过long long,保存在d中数值也不会超过int
运算优先级
(
)
()
()
n
o
t
not
not
∗
*
∗,
/
/
/,
m
o
d
mod
mod,
a
n
d
and
and
x
o
r
xor
xor,
+
+
+,
−
-
−,
o
r
or
or
=
=
=,
⩽
\leqslant
⩽,
⩾
\geqslant
⩾,
<
<
<,
>
>
>,
二进制状态压缩(状压DP)
成对变换
对于非负整数n
n为偶数:
n
x
o
r
1
=
n
+
1
n xor 1=n+1
nxor1=n+1
n为奇数 :
n
x
o
r
1
=
n
−
1
n xor 1=n-1
nxor1=n−1
多用于领接表中边集的存储,把一对正反方向的边(无向边)存成一对
l o w b i t lowbit lowbit运算
定义:非负整数
n
n
n在二进制表示下“最低位的1及其后边所有的0”构成的数值
推导略
函数求值
int lowbit(int a) {return a & (-a);}
0x02 递推与递归
递归递推定义
简单理解:
递推:倒推
递归:自身调用自身
分治
把一个问题划分为若干个规模更小的同类子问题,对子问题递归求解,回溯推导答案
递归的机器实现
原理不多说。(这个不是很重要)
注意事项:
1.不可声明过多或递归过深。
2.注意非局部变量的维护
3.递归转非递归
用一个数组模拟栈,使用变量模拟栈指针和返回值,使用
s
w
i
t
c
h
/
c
a
s
e
switch/case
switch/case或者
g
o
t
o
/
l
a
b
e
l
goto/label
goto/label模拟语句跳转
0x03 前缀和与差分
前缀和:前面所有值的和(不只是一维)
部分和:前缀和相减
差分:
设原序列A,差分序列B,
B
[
1
]
=
A
[
1
]
,
B
[
i
]
=
A
[
i
]
−
A
[
i
−
1
]
B[1] = A[1], B[i] = A[i] - A[i-1]
B[1]=A[1],B[i]=A[i]−A[i−1] ,
i
i
i
ϵ
\epsilon
ϵ
[
2
,
n
]
[2, n]
[2,n]
原序列A 的差分序列B的前缀和就是A
前缀和序列S的差分序列就是原序列A
应用:序列A区间
[
l
,
r
]
[l,r]
[l,r]加
d
d
d,相当于序列
B
[
l
]
+
1
,
B
[
r
+
1
]
−
d
B[l]+1,B[r+1]-d
B[l]+1,B[r+1]−d.
0x04 二分
基础用法
在单调序列和单调函数中进行查找
整数集合上的二分
注意:终止边界,左右区间取舍时的开闭情况,避免漏答案,造成死循环
二分终止条件:l==r
单调递增区间查找大于等于x的最小值
while(l < r){
int mid = (l + r) >> 1;
//关于这里为什么不是/2,而是>>1,是为了向下取整,负数也可以正常使用,上面0x01有提到两者区别。
if (a[mid] >= x) r = mid;
else l = mid + 1;
}
return a[l];
//因为单调性,如果a[mid]>=x,答案一定在mid前面(包括mid)
单调递增区间查找小于等于x的最大值
while(l < r){
int mid = (l + r + 1) >> 1;
//这里mid的取法要注意,如果不加1,容易进入死循环
if (a[mid] <= x) l = mid;
else r = mid - 1;
}
return a[l];
//因为单调性,如果a[mid]<=x,答案一定在mid后面(包括mid)
我们发现
mid = (l + r) >> 1;取不到l
mid = (l + r + 1) >> 1;取不到r
利用这个性质,我们把二分区间[1,n]分别扩大为[0,n][1,n+1],如果最后二分终止与这个越界下标,说明无解
实数域上的二分
保持精度
1.确定精度
e
p
s
eps
eps,以
l
+
e
p
s
<
r
l+eps<r
l+eps<r为循环条件,
e
p
s
=
eps =
eps=
1
0
−
(
k
−
2
)
10^{-(k-2)}
10−(k−2).
2.固定循环次数(for循环)
三分求单峰函数极值
单峰(谷)函数:有一个最大(小)值,越往外越小(大)【严格单调】【不严谨概念……】。
在
[
l
,
r
]
[l,r]
[l,r]上任取两点
l
m
i
d
,
r
m
i
d
lmid,rmid
lmid,rmid,把函数分成三段.
以单峰函数为例
if(f[lmid] < f[rmid]) 极大值必在lmid右侧
if(f[lmid] < f[rmid]) 极大值必在rmid左侧
0x05 排序
排序
名称 | 复杂度 |
---|---|
选择排序、插入排序、冒泡排序 | O( n 2 n^{2} n2) 比较法 |
堆排序、归并排序、快速排序 | O( n logn) 比较法 |
计数排序、基数排序、桶排序 | 与数值大小有关,运用位划分,分类映射等 |
离散化
把无穷大集合中的若干个元素映射为有限集合以便于统计
void discrete(){
sort(a+1, a+n+1);
for(int i = 1; i <= n; i++)
if(i == 1 || a[i] != a[i-1])
b[++m] = a[i];
}
int query(int x){
return lower_bound(b+1, b+m+1,x)-b;
}查询x映射为1~m之间的整数
关于lower_bound( )和upper_bound( )
1、在从小到大的排序数组中
lower_bound( begin,end,num)
从数组的begin位置到end-1位置二分查找第一个
>
=
n
u
m
>=num
>=num的数字,返回该数字的地址.
通过返回的地址减去起始地址begin,——>数字在数组中的下标。
upper_bound( begin,end,num)
从数组的begin位置到end-1位置二分查找第一个
>
>
>num的数字,找到返回该数字的地址
通过返回的地址减去起始地址begin——>数字在数组中的下标。
2、在从大到小的排序数组中
lower_bound( begin,end,num,greater<type>() )
从数组的begin位置到end-1位置二分查找第一个
<
=
<=
<=num的数字,找到返回该数字的地址
通过返回的地址减去起始地址begin,——>数字在数组中的下标。
upper_bound( begin,end,num,greater<type>() )
从数组的begin位置到end-1位置二分查找第一个<num的数字,找到返回该数字的地址
通过返回的地址减去起始地址begin,——>数字在数组中的下标。
中位数
这个就不用多说了吧……
有一些奇妙的应用,看例题吧
第K大数
给定
n
n
n个整数,如何求第
k
k
k大的数
快排后输出第k个数,复杂度是
O
O
O(
n
l
o
g
nlog
nlog
n
_n
n)
利用类似快排的思想,复杂度
O
(
n
)
O(n)
O(n)
快速排序时每次选择基准值后,把大的数放在左半边,统计出大于基准值的数的数量
c
n
t
cnt
cnt
如果
k
<
=
c
n
t
k<=cnt
k<=cnt,我们就在左半段(比基准值大的数中)寻找第
k
k
k大数
如果
k
>
c
n
t
k>cnt
k>cnt,我们就在右半段(小于等于基准值的数中)寻找第
k
−
c
n
t
k-cnt
k−cnt大数。
void work(int l, int r){
后补
}
逆序对
对于一个序列a,若
i
<
j
i < j
i<j且
a
[
i
]
>
a
[
j
]
a[i] > a[j]
a[i]>a[j],则称
a
[
i
]
>
a
[
j
]
a[i] > a[j]
a[i]>a[j]构成逆序对
使用归并排序求逆序对
a[]是待排序数组,b[]是临时数组,cnt是逆序对个数
void merge(int l, int mid, int r){
int i = l, j = mid+1;
for(int k = l; k <= r; k++)
if(j > r || i <= mid && a[i] <= a[j]) b[k] = a[i++];
else b[k] = a[j++], cnt += mid-i+1;
for(int k = l; k <= r; k++) a[k] = b[k];
}
0x06 倍增
通过成倍增长的方法,只递推状态空间中在
2
2
2的整数次幂位置上的值作为代表。
因为任意整数可以表示成若干个
2
2
2的次幂项的和,所需值可由求出的代表值拼凑。
倍增算法1
令
p
=
1
,
k
=
0
,
s
u
m
=
0
p=1, k=0, sum=0
p=1,k=0,sum=0
将要处理的数组
a
【
】
a【】
a【】与
a
[
p
]
a[p]
a[p] 相处理
不满足条件
p
∗
=
2
,
p *= 2,
p∗=2,超过条件
p
/
=
2
p /= 2
p/=2
直至
p
p
p 值为
0
0
0
ST算法
在
R
M
Q
RMQ
RMQ问题(区间最值问题)中常用
f
[
i
]
[
j
]
f[i][j]
f[i][j] 表示子区间
[
i
,
[i,
[i,
i
+
2
i+2
i+2
j
^{j}
j
−
1
]
-1]
−1]的所求值