文章目录
ACM笔记
二维数组与结构体
二维数组
- C语言中字符串输入输出
scanf("%s", str); // 无&
printf("%s", str);
- 二维数组本质上是以数组作为数组元素的数组,即“数组的数组”。
- 定义时,可以省略第一维的大小,但第二维的大小不可省略。
- 二维数组行优先。
- 二维数组的初始化可以按行分段赋值,也可以按行连续赋值。
int a[2][2] = {{1, 2}, {3, 4}};
int a[2][2] = {1, 2, 3, 4};
- 可以只对部分元素赋初值,未赋初值的元素自动取 0 。
int a[3][3] = {{1}, {2}, {3}};
结构体(struct) (c++)
swap(a, b)
交换两个变量的值,可以是结构体变量
排序与cmp函数
sort排序(c++)
sort()
复杂度n*log(n)- 头文件
#include <algorithm>
sort(起始地址, 结束地址, 排序方法(默认从小到大));
sort(a + i, a + j + 1);
从a[i]
到a[j]
sort(vis.begin() + i, vis.begin() + j + 1);
从vis[i]
到vis[j]
- 两种排序方法
- 从小到大
sort(a, a + n, less<要进行排序的数据类型>());
- 从大到小
sort(a, a + n, greater<要进行排序的数据类型>());
- 从小到大
cmp函数
- 将结构体元素按a从大到小排序
-
写法一:
int cmp(const sa &a, const sa &b) { return a.a > b.a; }
-
写法二:
bool cmp(sa a, sa b) { return a.a > b.a; }
-
gcd与lcm
预备知识
- 符号
- % 求余符号,a%b=r,a=kb+r
- | 整除符号,a|b,a能整除b,b=ka,b%a==0
- ≡ 同余符号,a≡𝑏(𝑚𝑜𝑑 𝑐),同余式,a%c=b%c
- 计算
- (a + b) % p = (a % p + b % p) % p
- (a - b) % p = (a % p - b % p) % p
- (a b) % p = (a % p b % p) % p
- a ^ b % p = ((a % p)^b) % p
求最大公约数
-
辗转相除法(欧几里得算法)(gcd) O(log(n))
int gcd(int a, int b) { return b ? gcd(b, a % b) : a; }
-
辗转相减法(更相减损术)O(n)特判零和负数
-
素因子法
求最大公倍数
-
lcm()
int lcm(int a, int b) { return a / gcd(a, b) * b; }
gcd的性质
- 计算
- gcd(a , b) = gcd(b , a-b)
- gcd(ma , mb) = m*gcd(a , b), m为一个自然数
- gcd(a+mb , b) = gcd(a , b)
- m=gcd(a , b) 则gcd(a/m,b/m)=gcd(a,b)/m
- gcd(a, lcm(b, c)) = lcm(gcd(a, b), gcd(a, c))
- lcm(a, gcd(b, c)) = gcd(lcm(a, b), lcm(a, c))
- n个数的gcd则两两合并
快速幂
样板函数
ll fastPower(ll base, ll power)
{
ll result = 1;
while (power > 0)
{
if (power & 1)
{
result = result * base % 1000; // 后三位
}
power >>= 1;
base = (base * base) % 1000; // 后三位
}
return result;
}
二进制与二进制枚举
二进制操作
& | ~ ^
与、或、非、异或- 异或:同0异1,不进位的二进制加法,同一值异或两次为原值
<< >>
左移乘2,右移除2
二进制枚举
-
原理:二进制下n位数有2n个,n个元素的集合的子集也有2n个
-
操作
for (int i = 1; i < (1 << n); i++) { for (int j = 0; j < n; j++) { if (i & (1 << j)) // 判断各位是0还是1 { // 1 } else { // 0 } } }
素数筛 //有待完善
实现方法
-
不知名方法 O(nlog(n))
vector<int> su; const int N = 6000; void prime(int n) // 找n以内的素数 { int flag[N]; memset(flag, 1, sizeof(flag)); flag[0] = flag[1] = 0; su.push_back(0); for (int i = 2; i <= n; i++) { if (flag[i]) { su.push_back(i); for (int j = i * 2; j <= n; j += i) { flag[j] = 0; } } } return; }
-
埃氏筛 O(nlog(log(n)))
vector<int> su; const int N = 6000; void prime(int n) // 找n以内的素数 { int flag[N]; memset(flag, 1, sizeof(flag)); flag[0] = flag[1] = 0; su.push_back(0); for (int i = 2; i <= n; i++) { if (flag[i]) { su.push_back(i); for (int j = i * 2; j <= n; j += i) { flag[j] = 0; } } } return; }
-
线性筛 O(n)
解释:我们要筛1n中的素数,然后先默认他们都是素数,最外层枚举1n的所有数,如果它是素数,就加到素数表,对于每一个枚举的i ,枚举素数表里的数,然后素数就会标记自己 i倍的数不是素数,(素数的倍数不是素数)枚举素数表什么时候停?枚举到i的最小质因子,标记完就可以停了,保证每个数只被他的最小质因子筛掉。例如:外层i=15时,素数表里:2,3,5,7,11,13 215=30,把30筛掉;315=45,把45筛掉,因为15%3==0,退出里面的循环;15是被3筛掉的,因为3是15的最小素因子。const int N = 500002; bool flag[N]; int prime[N]; int cnt; void getprime(int n) { memset(flag, 1, sizeof(flag)); flag[0] = flag[1] = cnt = 0; for (int i = 2; i <= n; i++) { if (flag[i] == 1) prime[++cnt] = i; for (int j = 1; j <= cnt && prime[j] * i <= n; j++) { flag[i * prime[j]] = 0; if (i % prime[j] == 0) break; } } }
-
唯一分解定理
-
求因子和
队列
基本操作
- 头文件
#include <queue>
- 初始化
queue<typename>vis;
- 入队
vis.push(x);
- 出队
vis.pop();
- 判断空队列
vis.empty();
- 元素数量
vis.size();
- 队首元素
vis.front();
- 队尾元素
vis.back();
栈
定义
一组地址连续的存储单元依次自栈底到栈顶存放栈的数据元素
基本操作
- 头文件
#include <stack>
- 初始化
stack<typename>vis;
- 入栈
vis.push(x);
- 出栈
vis.pop();
- 判断空栈
vis.empty();
- 元素数量
vis.size();
- 栈顶元素
vis.top();
string
基本操作
-
头文件
#include <string>
-
定义
string str;
-
初始化
string str="abcd";
-
访问
-
下标
for (int i = 0; i < str.length(); i++) printf("%c", str[i]);
-
迭代器 可加减
str.begin()+2
for (string::iterator it = str.begin(); it != str.end(); it++) printf("%c", *it);
-
-
输入输出(没有其他方式)
cin
cout
printf("%s",str.c_str());
-
运算
- 加法:拼接字符串(变量
str
、常量"123"
)、字符(单字符'1'
、字符串元素str[0]
) - 关系:按字典序比较字符串变量
- 加法:拼接字符串(变量
-
函数
- 长度
str.lengh();
str.size();
O(1) - 清空
str.clear();
O(1) - 子串:从下标pos开始长度为len
str.substr(pos,len);
O(n) - 插入 O(n)
- 在下标pos插入str1
str.insert(pos,str1);
- 在it上插入it2到it3的字符串(左闭右开区间)
str.insert(it, it2, it3);
- 在下标pos插入str1
- 删除 O(n)
- 单个元素
str.erase(it);
- 迭代器从first到last左闭右开区间
str.erase(first,last);
- 从下标pos开始长度为len
str.erase(pos,len);
- 单个元素
- 查找 O(n*m)
- 匹配str1(字符串、字符),返回第一次出现的下标或-1(
string::npos
)str.find(str1);
- 从下标pos开始匹配
str.find(str1,pos);
- 匹配str1(字符串、字符),返回第一次出现的下标或-1(
- 替换 O(n)
str.replace(pos,len,str1);
相当于str.erase(pos,len);
后str.insert(pos,str1);
str.replace(it1,it2,str1);
相当于str.erase(it1,it2);
后str.insert(pos,str1);
- 长度
其他
-
max(x,y)
min(x,y)
适用于基本数据类型,可套用 -
abs(x)
fabs(x)
分别适用于整型和浮点型,返回绝对值 -
swap(x,y)
交换值 -
reverse()
reverse(a+i,a+j+1);
反转a[i]
到a[j]
的元素reverse(it1,it2);
反转容器的it1到it2(左闭右开)元素
-
next_permutation()
从a[i]
到a[j]
,先排序得到最小字典序,再循环全排列int a[n]; sort(a + i, a + j + 1); do { } while (next_permutation(a + i, a + j + 1));
-
fill()
memset()
fill()
按单元在数组或容器的某一区间内赋值fill(a + i, a + j + 1, x);
从a[i]到a[j]fill(vis.begin() + i, vis.begin() + j + 1, x);
从vis[i]到vis[j]
memset()
按字节给数组赋值
map、pair与set
这里不可以在迭代器上直接加减!!!
map
-
头文件
#include <map>
-
初始化
map<typename1,typename2> vis;
-
访问
-
下标
vis[‘c’]=124;
-
迭代器
map<typename1, typename2>::iterator it; it->first; it->second;
-
-
函数
- 映射对数
vis.size();
O(1) - 清空
vis.clear();
O(n) - 查找:
- 返回键值key的映射的迭代器
vis.find(key);
O(log(n)) - 返回值是bool值
vis.count(key)
- 返回键值key的映射的迭代器
- 删除
- 单个元素
vis.erase(it);
O(1)vis.erase(key);
O(log(n)) - 迭代器从first到last左闭右开区间
vis.erase(first,last);
O(n)
- 单个元素
- 映射对数
-
map会自动按照key进行排序
pair
-
定义:二元结构体
struct pair { typename1 first; typename2 second; };
-
头文件
#include <utility>
map头文件自带utility -
初始化
pair<typename1,typename2> vis;
set
-
特点:自动去重、升序排序、内部用红黑树实现
-
头文件
#include <set>
-
初始化
set<typename> vis;
-
访问:迭代器
for (set<typename>::iterator it = vis.begin(); it != vis.end(); it++) cout << *it;
-
函数
- 元素个数
vis.size();
- 清空
vis.clear();
- 插入
vis.insert(x);
- 删除
- 单个元素
vis.erase(it);
vis.erase(x);
- 迭代器从first到last左闭右开区间
vis.erase(first,last);
- 单个元素
- 查找:存在1不存在0
vis.count(x);
- 特殊定位 要升序排列
vis.lower_bound(x);
返回第一个大于等于x的定位器vis.upper_bound(x);
返回第一个大于x的定位器
- 元素个数
vector
基本操作
-
头文件
#include <vector>
-
初始化
vector<typename> vis;
-
访问
-
下标
for (int i = 0; i < vis.size(); i++) cout << vis[i];
-
迭代器 可加减
vis.begin()+2
for (vector<typename>::iterator it = vis.begin(); it != vis.end(); it++) cout << *it;
-
-
函数
- 元素个数
vis.size();
可用于二维vis[i].size();
O(1) - 清空
vis.clear();
O(n) - 添加
vis.push_back(x);
O(1) - 删除尾元素
vis.pop_back();
O(1) - 插入:在迭代器it处插入x
vis.insert(it,x);
O(n) - 删除
- 单个元素
vis.erase(it);
- 迭代器从first到last左闭右开区间
vis.erase(first,last);
- 单个元素
- 元素个数
优先队列
基本操作
- 头文件
#include <queue>
- 初始化:通常为结构体
priority_queue<typename> vis;
- 队首元素(堆顶元素)
- 访问
vis.top();
- 删除
vis.pop();
- 访问
排序比较
-
重载:样板写法
struct sa { int id; int time; int now; friend bool operator<(sa a, sa b) { if (a.now != b.now) return a.now > b.now; else return a.id > b.id; } };
-
手写堆
- 小根堆:从小到大排列
priority_queue<typename,vector<typename>,greater<typename> > vis;
- 大根堆:从大到小排列
priority_queue<typename,vector<typename>,less<typename> > vis;
- 小根堆:从小到大排列
ACM公选课
- 题目复杂度
1e7*4=1s
- 字符串输入
- C
char ch[100]; gets(ch);
- C++ 可加入第三个参数作结束标识符,但要注意换行符的问题//???
char ch[100]; cin.getline(ch,100);
string str; getline(cin,str);
- C
- 文件处理
freopen()``FILE * freopen(const char *__restrict__ _Filename, const char *__restrict__ _Mode, FILE *__restrict__ _File)
将输入或输出流由键盘缓存区重定向至文件,不能做到既文件读取又键盘读入fopen()``FILE * fopen(const char *__restrict__ _Filename, const char *__restrict__ _Mode)
- 快速幂可以到ll
- n^n的个位数字循环,在初始化二维数组时可以把循环节最后一个数字放在第一,以方便用
n%循环节
来确定 - n的阶乘用log10()来计算
- 任意数的位数为
(int)log10(x)+1
- 大数的最高位为
d = (int)(10^(log10(x)-(int)log10(x));
- 一个变量对一个数(<=300)疯狂取余,用循环节解决
- 7——49
- 11——100
- 100——500
- 300——1000
- 动态找周期:一般判断前两个相同即可
- 逆元法
hash
- 常用哈希函数
- 进制哈希
H
a
s
h
(
s
)
=
∑
i
=
0
∣
s
∣
−
1
s
[
i
]
⋅
p
i
Hash(s)= \sum_{i=0}^{\vert s\vert - 1} {s[i]\cdot p^i}
Hash(s)=i=0∑∣s∣−1s[i]⋅pi
- 优势:对子串哈希友好
- 后缀
i
(倒数第i
个)的哈希值 H a s h s ( i ) = { 0 , i = ∣ s ∣ H a s h s ( i + 1 ) ⋅ p + s [ i ] , 0 ≤ i < ∣ s ∣ Hash_s(i) = \begin{cases} 0, & i=\vert s\vert \\ Hash_s(i+1)\cdot p + s[i], & 0\leq i < \vert s \vert \end{cases} Hashs(i)={0,Hashs(i+1)⋅p+s[i],i=∣s∣0≤i<∣s∣ - 从
i
起始,长度为len
的子串 H a s h s ( i , l e n ) = H a s h s ( i ) − H a s h s ( i + l e n ) ⋅ p l e n Hash_s(i,len)= Hash_s(i)- Hash_s(i+len)\cdot p^{len} Hashs(i,len)=Hashs(i)−Hashs(i+len)⋅plen H a s h s ( i , l e n ) = H a s h s ( i + 1 , l e n ) ⋅ p − s [ i + l ] ⋅ p l e n + s [ i ] Hash_s(i,len)= Hash_s(i+1,len)\cdot p-s[i+l]\cdot p^{len}+s[i] Hashs(i,len)=Hashs(i+1,len)⋅p−s[i+l]⋅plen+s[i]
- 进制哈希
H
a
s
h
(
s
)
=
∑
i
=
0
∣
s
∣
−
1
s
[
i
]
⋅
p
i
Hash(s)= \sum_{i=0}^{\vert s\vert - 1} {s[i]\cdot p^i}
Hash(s)=i=0∑∣s∣−1s[i]⋅pi
- 说明:
- 针对某一哈希算法的冲突数据需经过特意挑选
- 由概率论知:对N取模时超过 N \sqrt{N} N会哈希冲突
KMP算法
- 最大公共前后缀,next数组