v1.6
最近更新:2021/12/9
目录
一、头文件
#include<cstdio>
#include<cstring>
#include<stdlib.h>
#include<algorithm>
#include<iostream>
#include<vector>
#include<set>
#include<string>
#include<map>
#include<queue>
#include<stack>
#include<math.h>
using namespace std;
const int INF = 0x3f3f3f3f;
注:math.h中,abs(x)求整数x绝对值,
fabs(x)求double型x绝对值,
ceil(x)取上整,floor(x)取下整,
pow(x,y) x的y次方,sqrt(x)开根
fabs、ceil、floor、pow、sqrt均返回double型
二、排序与查找
1、快速排序
void quickSort(int a[],int l,int r){ //快速排序
if(l < r){
int pos = partition(a, l, r);
quickSort(a, l, pos-1);
quickSort(a, pos+1, r);
}
}
int partition(int a[],int l,int r){ //快排的一趟
int temp = a[l];
while(l < r){
while (l < r && temp <= a[r]) r--;
a[l] = a[r];
while(l < r && temp >= a[l]) l++;
a[r] = a[l];
}
a[l] = temp;
return l;
}
2、归并排序
//非递归实现
void merge(int A[], int L1, int R1, int L2, int R2){ //将数组A的有序的[L1,R1]与[L2,R2]区间合并为一个有序区间。
int i = L1, j = L2;
int temp[maxn], index = 0;
while(i <= R1 && j <= R2){
if(A[i] <= A[j]){
temp[index++] = A[i++];
} else{
temp[index++] = A[j++];
}
}
while(i <= R1) temp[index++] = A[i++];
while(j <= R2) temp[index++] = A[j++];
for(i = 0; i < index; i++){
A[L1 + i] = temp[i];
}
}
void mergeSort(int A[]){ //主。额外注明:元素个数为n,数组下标从1开始。
for(int step = 2; step / 2 <= n; step *= 2){ //step表示当前要合并的一对长度和的最大值
for(int i = 1; i <= n; i += step){
int mid = i + step / 2 - 1;
if(mid + 1 <= n){
merge(A, i, mid, mid+1, min(i + step - 1, n));
}
}
}
}
3、二分查找与快速幂
//假设不含重复元素,在递增序列A中进行二分查找,目标为x,找不到返回-1
int binarySearch(int A[], int left, int right, int x){
int mid;
while(left <= right){
mid = (left + right) / 2;
if(A[mid] == x) return mid;
else if(A[mid] > x) right = mid - 1;
else left = mid + 1;
}
return -1;
}
//假设可能含重复元素,lower_bound返回第一个值 >= x 的元素的位置 L,
//upper_bound返回第一个值 > x 的元素的位置 R
//如果A的最后一个元素是x,则x的个数是R-L+1。其余任何情况x的个数都是R-L。
int lower_bound(int A[], int left, int right, int x){
int mid;
while(left < right){
mid = (left + right) / 2;
if(A[mid] >= x) right = mid;
else left = mid + 1;
}
return left;
}
int upper_bound(int A[], int left, int right, int x){
int mid;
while(left < right){
mid = (left + right) / 2;
if(A[mid] > x) right = mid;
else left = mid + 1;
}
return left;
}
typedef long long LL;
//快速幂的递归写法,计算a^b % m
LL binaryPow(LL a, LL b, LL m){
if(b == 0) return 1;
if(b & 1) return a * binaryPow(a, b - 1, m) % m;
else{
LL mul = binaryPow(a, b / 2, m);
return mul * mul % m;
}
}
//快速幂的迭代写法,计算a^b % m
LL binaryPow(LL a, LL b, LL m){
LL ans = 1;
while(b > 0){
if(b & 1) ans = ans * a % m;
a = a * a % m;
b >>= 1; //注:等价于b = b >> 1; 相当于b除以2
}
return ans;
}
4、二分查找(二分使用该板子)
二分的本质不是单调性,能二分不一定要有单调性。二分的本质如下:假设有一整数序列,有一性质,使得某一个数右边(含该数)满足该性质,而该数左边(不含该数)不满足该性质。形象地表示,假设A是不满足该性质的数,B是满足该性质的数
AAAAAABBBBBBB
则二分可以寻找该性质的分界点,即既能寻找最右边的A,又能寻找最左边的B。
情况1:找最右边的A
mid = (l + r + 1) / 2; //注意加一!
if(检查mid是否满足A的性质)
如果true,答案在[mid, r],令l=mid即可。
如果false,答案在[l, mid-1],令r=mid - 1即可。
情况2:找最左边的B
mid = (l + r) / 2;
if(检查mid是否满足B的性质)
如果true,答案[l,mid],令r = mid即可。
如果false,答案[mid+1,r],令l=mid+1即可。
(以上循环条件l < r,注:循环结束时一定有 l = r)
现在解释为什么情况1里mid那要+1:当l=r-1时,mid = (l+r)/2 是等于l的,如果检查条件返回true,则死循环。因此补上+1。二分时,mid左右偏移一点完全不影响效率和正确性(只要不死循环)。
二分一定会停止,之后判断是否为题目中的无解情况,即判断序列[l]号元素是否满足要求。
以上情况对应的二分板子如下(推荐该板子,情况完备且有定式):
4、二分查找(二分使用该板子)
// 情况1:找最右边的A时使用:
int bsearch_1(int l, int r)
{
while (l < r)
{
int mid = l + r + 1 >> 1;
if (check(mid)) l = mid; // check()判断mid是否满足A的性质
else r = mid - 1;
}
return l;
}
// 情况2:找最左边的B时使用:
int bsearch_2(int l, int r)
{
while (l < r)
{
int mid = l + r >> 1;
if (check(mid)) r = mid; // check()判断mid是否满足B的性质
else l = mid + 1;
}
return l;
}
//注:check()判断 是否满足需要找的那个点所在分类的条件,满足为True
记忆:
找最右 ~ mid多加1 ~ 左为mid(check条件后跟的是l=mid)
找最左 ~ mid不加1 ~ 右为mid
三、二叉树
1、存储结构与基本操作
struct node{
int data;
node* lchild;
node* rchild;
};
node* newNode(int v){ //生成新结点,v为结点权值
node* Node = new node;
Node->data = v;
Node-> lchild = Node->rchild = NULL;
return Node;
}
void search(node* root,int x,int newdata){ //查找值为x的所有结点,并把它们的值修改为newdata
if(root == NULL){
return;
}
if(root->data == x){
root->data = newdata;
}
search(root->lchild,x,newdata);
search(root->rchild,x,newdata);
}
void insert(node* &root,int x){ //插入值为x的新结点,查找失败的位置即插入位置,注意引用
if(root == NULL){
root = newNode(x);
return;
}
if(x应该插在左子树){
insert(root->lchild,x);
}
else{
insert(root->rchild,x);
}
}
node* Create(int data[],int n){ //创建二叉树,插入的数据在数组中
node* root = NULL;
for(int i = 0; i < n; i++){
insert(root,data[i]);
}
return root;
}
2、遍历
void preorder(node* root){ //先序遍历 根左右
if(root == NULL){
return;
}
visit(root);
preorder(root->lchild);
preorder(root->rchild);
}
void inorder(node* root){ //中序遍历 左根右
if(root == NULL){
return;
}
inorder(root->lchild);
visit(root);
inorder(root->rchild);
}
void postorder(node* root){ //后序遍历 左右根
if(root == NULL){
return;
}
postorder(root->lchild);
postorder(root->rchild);
visit(root);
}
void LayerOrder(node* root){ //层序遍历,为记录结点所在层数,在二叉树结构体中添加 int layer;
queue<node*> q;
root->layer = 1;
q.push(root);
while(!q.empty()){
node* now = q.front();
q.pop();
visit(now);
if(now->lchild != NULL){
now->lchild->layer = now->layer + 1;
q.push(now->lchild);
}
if(now->rchild != NULL){
now->rchild->layer = now->layer + 1;
q.push(now->rchild);
}
}
}
3、先序序列、中序序列构造二叉树
node* create(int preL,int preR,int inL,int inR){ //给定先序遍历序列pre[]、中序遍历序列in[],重建二叉树。[preL,preR]为先序序列区间,[inL,inR]为中序序列区间。
if(preL > preR){
return NULL;
}
node* root = new node;
root->data = pre[preL];
int k;
for(k = inL; k <= inR; k++){
if(in[k] == pre[preL]){
break;
}
}
int numLeft = k - inL;
root->lchild = create(preL + 1,preL + numLeft,inL,k-1);
root->rchild = create(preL + numLeft + 1,preR,k + 1,inR);
return root;
}
4、二叉查找树的基本操作
//(1)二叉查找树的插入与建立
void insert(node* &root, int x){ //将x插入以root为根的二叉查找树中,递归地
if(root == NULL){
root = newNode(x);
return;
}
if(x == root->data){
return;
}
else if(x < root->data){
insert(root->lchild, x);
}
else{
insert(root->rchild, x);
}
}
node* Create(int data[], int n){ //将data[]构造成一棵二叉查找树,返回根结点指针
node* root = NULL;
for(int i = 0; i < n; i++){
insert(root, data[i]);
}
return root;
}
//(2)二叉查找树的查找
void search(node* root, int x){ //查找数据为x的结点
if(root == NULL){
printf("search failed\n"); //查找失败
return;
}
if(x == root->data){
//查找成功,在此处进行访问
}
else if(x < root->data){
search(root->lchild, x);
}
else{
search(root->rchild, x);
}
}
//(3)二叉查找树的删除,复杂一些。思路:删结点a -> 用a的前驱(或后继)替代a,删除a的前驱(或后继)。边界:删除叶子。递归进行
node* findMax(node* root){ //辅助函数:寻找以root为根的二叉查找树中最大结点
while(root->rchild != NULL){
root = root->rchild;
}
return root;
}
node* findMin(node* root){ //辅助函数:寻找以root为根的二叉查找树中最小结点
while(root->lchild != NULL){
root = root->lchild;
}
return root;
}
void deleteNode(node* &root, int x){ //在以root为根的二叉查找树中删除值为x的结点。
if(root == NULL){ //边界:不存在值为x的结点
return;
}
if(root->data == x){ //找到了x,删除它
if(root->lchild == NULL && root->rchild == NULL){ //边界:叶子结点,直接删除
root = NULL;
}
else if(root->lchild != NULL){ //左结点不空,就找前驱递归
node* pre = findMax(root->lchild);
root->data = pre->data;
deleteNode(root->lchild, pre->data);
}
else if(root->rchild != NULL){ //右结点不空,就找后继递归
node* next = findMin(root->rchild);
root->data = next->data;
deleteNode(root->rchild, next->data);
}
}
else if(x < root->data){ //往左查找
deleteNode(root->lchild, x);
}
else if(x > root->data){ //往右查找
deleteNode(root->rchild, x);
}
}
5、平衡二叉树的建立
struct node{
int v, height;
node *lchild, *rchild;
};
node* newNode(int v){ //用于生成新结点
node* Node = new node;
Node->v = v;
Node->height = 1;
Node->lchild = Node->rchild = NULL;
return Node;
}
int getHeight(node* root){ //获得以root结点为根的子树的高度
if(!root) return 0;
return root->height;
}
int getBalanceFactor(node* root){ //获得root结点的平衡因子
return getHeight(root->lchild) - getHeight(root->rchild);
}
void updateHeight(node* root){ //更新root的height值
root->height = max(getHeight(root->lchild), getHeight(root->rchild)) + 1;
}
void L(node* &root){ //对root进行左旋操作。即root的右结点作新的根,root作为其左结点,其原来的左结点作为root的右结点
node* temp = root->rchild;
root->rchild = temp->lchild;
temp->lchild = root;
updateHeight(root);
updateHeight(temp);
root = temp;
}
void R(node* &root){//对root进行右旋操作。即root的左结点作新的根,root作为其右结点,其原来的右结点作为root的左结点
node* temp = root->lchild;
root->lchild = temp->rchild;
temp->rchild = root;
updateHeight(root);
updateHeight(temp);
root = temp;
}
void insert(node* &root, int v){ //插入权值为v的结点
if(!root){
root = newNode(v);
return;
}
if(v < root->v){ //v比root小,则往左插
insert(root->lchild, v); //往左递归插入
updateHeight(root); //更新树高
if(getBalanceFactor(root) == 2){ //如果失衡则进行相应调整
if(getBalanceFactor(root->lchild) == 1){ //LL型
R(root);
}
else if(getBalanceFactor(root->lchild) == -1){ //LR型
L(root->lchild);
R(root);
}
}
}
else{ //v比root大,以下同理。
insert(root->rchild, v);
updateHeight(root);
if(getBalanceFactor(root) == -2){
if(getBalanceFactor(root->rchild) == -1){ //RR型
L(root);
}
else if(getBalanceFactor(root->rchild) == 1){ //RL型
R(root->rchild);
L(root);
}
}
}
}
node* create(int data[], int n){ //建立AVL树
node* root = NULL;
for(int i = 0; i < n; i++){
insert(root, data[i]);
}
return root;
}
四、树
1、静态存储结构与基本操作
struct node{
int data;
vector<int> child;
} Node[maxn];
//新建一个结点
int index = 0;
int newNode(int v){
Node[index].data = v;
Node[index].child.clear();
return index++;
}
2、遍历
void PreOrder(int root){ //树的先根遍历 根 子树,注:递归边界隐含在for循环的结束条件中,但是调用传入的root不能是空结点。
visit(Node[root]);
for(int i = 0; i < Node[root].child.size(); i++){
PreOrder(Node[root].child[i]);
}
}
void LayerOrder(int root){ //树的层序遍历,为记录结点所在层数,在树的结构体中添加 int layer;
queue<int> Q;
Q.push(root);
Node[root].layer = 0;
while(!Q.empty()){
int front = Q.front;
Q.pop();
visit(Node[front]);
for(int i = 0; i < Node[front].child.size(); i++){
int child = Node[front].child[i];
Node[child].layer = Node[front].layer + 1;
Q.push(child);
}
}
}
3、堆
//以大顶堆为例,堆是一棵完全二叉树,用数组存储,根节点在数组中下标为1,结点i的左子树下标为 i * 2,右子树下标为i * 2 + 1(如果存在)
//建堆:对于一棵乱序的完全二叉树,通过调整使其成为堆。由于用的是数组存储,是在一个乱序序列上建堆。
//设n为元素个数,heap[]是表示堆的数组
void createHeap(){ //时间复杂度:O(n)
for(int i = n/2; i>= 1; i--){
downAdjust(i,n);
}
}
void downAdjust(int low, int high){ //向下调整,对heap在[low,high]范围内进行向下调整(high一般设置为n)。这一趟把low往下调整到正确位置。
int i = low, j = i * 2;
while(j <= high){
if(j+1 <= high && heap[j+1] > heap[j]){
j = j + 1;
}
if(heap[j] > heap[i]){
swap(heap[j], heap[i]);
i = j;
j = i * 2;
}
else{
break;
}
}
}
//删除堆顶元素:用最后一个元素覆盖堆顶元素,再对根节点向下调整。时间复杂度O(logn)
void deleteTop(){
heap[1] = heap[n];
n--;
downAdjust(1, n);
}
//往堆中添加一个元素:把添加的元素放在最后,进行向上调整。
void insert(int x){
n++;
heap[n] = x;
upAdjust(1, n);
}
void upAdjust(int low, int high){ //向上调整,对heap在[low,high]范围内进行向上调整(low一般设置为1)
int i = high, j = i / 2;
while(j >= low){
if(heap[j] < heap[i]){
swap(heap[j], heap[i]);
i = j;
j = i / 2;
}
else{
break;
}
}
}
/*堆排序*/
//思路:(以大顶堆为例)建堆后,取出堆顶元素,将堆的最后一个元素移至堆顶,对堆顶向下调整,直到堆只有一个元素。
//给定一个初始乱序数组序列heap,调整后使其有序
void heapSort(){
createHeap();
for(int i = n; i > 1; i--){
swap(heap[i], heap[1]);
downAdjust(1, i - 1);
}
}
4、并查集
int father[maxn]; //father[i]表示i的父亲结点,若father[i] == i,则i是该集合的根结点。一个集合是一棵树(根有自环)
//初始化:father[i] = i;
int findfather(int x){ //(简单版)查找。返回元素x所在集合的根结点
while(x != father[x]){
x = father[x];
}
return x;
}
int findfather(int x){ //(路径压缩版)查找。均摊后时间复杂度降低为近似O(1)。
int a = x;
while(x != father[x]){
x = father[x];
}
while(a != father[a]){
int z = a;
a = father[a]; //注意顺序
father[z] = x;
}
return x;
}
void Union(int a, int b){ //合并。合并集合a、集合b。 注:Union中U必须大写,否则是关键字。
int faA = findfather(a);
int faB = findfather(b);
if(faA != faB){
father[faA] = faB;
}
}
五、图
1、图的存储
//1邻接矩阵
int G[maxn][maxn];
//2邻接表,一个表存放一个顶点的所有出边
//第一种:邻接表只存边的终点编号,不存边权
vector<int> Adj[maxn]; //Adj有maxn个vector,vector元素为int,存放结点的出边终点。
//第二种:邻接表存边的终点编号、边权
struct Node{
int v; //出边的终点的编号
int w; //边权
Node(int _v, int _w){ //构造函数
v = _v;
w = _w;
}
};
vector<Node> Adj[maxn]; //Adj有maxn个vector,vector元素为Node,存放结点的出边终点以及对应边权。
2、DFS遍历图
const int maxn = 1000;
const int INF = 0x3f3f3f3f;
//2.1邻接矩阵版
int n,G[maxn][maxn]; //n为顶点数
bool vis[maxn] = {false};
void DFS(int u, int depth){ //u为顶点编号,depth为深度,从1开始
vis[u] = true;
//在此进行对u顶点的操作
for(int v = 0; v < n; v++){
if(vis[v] == false && G[u][v] != INF){
DFS(v, depth + 1);
}
}
}
void DFSTrave(){
for(int u = 0; u < n; u++){
if(vis[u] == false){
DFS(u, 1);
}
}
}
//2.2邻接表版(使用第二种邻接表,即邻接表存边的终点编号、边权)
vector<Node> Adj[maxn];
int n; //n为顶点数
bool vis[maxn] = {false};
void DFS(int u, int depth){
vis[u] = true;
//在此进行对u顶点的操作
for(int i = 0; i < Adj[u].size(); i++){
int v = Adj[u][i].v;
if(vis[v] == false){
DFS(v, depth + 1);
}
}
}
void DFSTrave(){
for(int u = 0; u < n; u++){
if(vis[u] == false){
DFS(u, 1);
}
}
}
3、BFS遍历图
//3.1邻接矩阵版
int n,G[maxn][maxn];
bool vis[maxn] = {false};
void BFS(int u){
queue<int> q;
q.push(u);
vis[u] = true;
while(!q.empty()){
int now = q.front();
q.pop();
//在此对now顶点进行操作
for(int v = 0; v < n; v++){
if(vis[v] == false && G[now][v] != INF){
q.push(v);
vis[v] = true;
}
}
}
}
void BFSTrave(){
for(int u = 0; u < n; u++){
if(vis[u] == false){
BFS(u);
}
}
}
//3.2邻接表版
vector<Node> Adj[maxn];
int n;
bool vis[maxn] = {false};
void BFS(int u){
queue<int> q;
q.push(u);
vis[u] = true;
while(!q.empty()){
int now = q.front();
q.pop();
//在此对now顶点进行操作
for(int i = 0; i < Adj[now].size(); i++){
int v = Adj[now][i].v;
if(vis[v] == false){
q.push(v);
vis[v] = true;
}
}
}
}
void BFSTrave(){
for(int u = 0; u < n; u++){
if(vis[u] == false){
BFS(u);
}
}
}
4、最短路径Dij + DFS
//4.1 输入图G后,使用Dijkstra 获得起点s到其他节点所有最短路长 + 前驱
//(邻接表版Dij)
vector<Node> Adj[maxn];
int d[maxn]; //记录起点到结点i的最短距离
vector<int> pre[maxn]; //pre[i]表示起点到结点i的最短路径里,结点i的前驱结点
bool vis[maxn] = {false};
void Dij(int s){ //s为起点
fill(d, d + maxn, INF); //memset(d, INF, sizeof(d)); 头文件cstring
d[s] = 0;
for(int i = 0; i < n; i++){
int u = -1, MIN = INF; //找出未访问的点里最小的
for(int j = 0; j < n; j++){
if(vis[j] == false && d[j] < MIN){
u = j;
MIN = d[j];
}
}
if(u == -1) return; //剩下的结点不连通,结束
vis[u] = true;
for(int j = 0; j < Adj[u].size(); j++){
int v = Adj[u][j].v;
if(vis[v] == false){
if(d[u] + Adj[u][j].w < d[v]){
d[v] = d[u] + Adj[u][j].w;
pre[v].clear();
pre[v].push_back(u);
}
else if(d[u] + Adj[u][j].w == d[v]){
pre[v].push_back(u);
}
}
}
}
}
//4.2 起点->终点有多条最短路,在其中找到使第二标尺最优的路径,使用DFS
//前提:在4.1的基础上,设st为路径起点。
int optvalue = 初值0或INF; //第二标尺最优值
vector<int> path, temppath; //最优路径、临时路径,倒着存的
void DFS(int v){ //初始调用传入终点编号
if(v == st){ //边界:到达起点
temppath.push_back(v);
int value;
//此处计算temppath上的value值,涉及边权或者点权
if(value优于optvalue){
optvalue = value;
path = temppath;
}
temppath.pop_back();
return;
}
temppath.push_back(v);
for(int i = 0; i < pre[v].size(); i++){
DFS(pre[v][i]);
}
temppath.pop_back();
}
5、Floyd算法
//思想:若存在点k,使得k作中介能使i->j的最短路径缩短,则用k作中介。可以有负边权,但不能有负权回路。
//设n为结点数。时间复杂度O(n^3),限制了n<=200
int dis[maxn][maxn];//dis[i][j]初始存i->j的边权,Floyd运算后dis[i][j]表示i->j的最短距离
void Floyd(){
for(int k = 0; k < n; k++){
for(int i = 0; i < n; i++){
for(int j = 0; j < n; j++){
if(dis[i][k] + dis[k][j] < dis[i][j] && dis[i][k] != INF && dis[k][j] != INF){
dis[i][j] = dis[i][k] + dis[k][j];
}
}
}
}
}
/*
附:dis[][]的初始化过程:1、全部赋INF 2、dis[i][i] = 0
此外,输入dis[i][j]的值为 w 时,若为无向图,记得dis[j][i] 也为 w
*/
6、拓扑排序
//邻接表实现。(1)拓扑序列:若图中存在边u->v,则序列中u在v前面。(2)拓扑排序可判断图是否为有向无环图。
vector<int> Adj[maxn]; //拓扑排序和权值没关系,此处以第一种邻接表为例
int n, m, inDegree[maxn]; //顶点数,边数,每个结点的入度
bool tppx(){ //返回true说明拓扑排序成功,图是有向无环图。否则图中有环。
int num = 0;
queue<int> q;
for(int i = 0; i < n; i++){ //入度为0入队
if(inDegree[i] == 0){
q.push(i);
}
}
while(!q.empty()){
int u = q.front();
q.pop();
//访问该结点,如printf("%d",u);
for(int i = 0; i < Adj[u].size(); i++){
int v = Adj[u][i];
inDegree[v]--;
if(inDegree[v] == 0){
q.push(v);
}
}
//可清空u的所有出边,也可不写。Adj[u].clear();
num++;
}
if(num == n) return true;
else return false;
}
//注:若题目要求有多个入度为0的点选择编号最小的,则把queue改成priority_queue,保证队首元素是最小的。
六、其他
1、大整数运算
struct bign{
int d[1000]; //数的低位存在低下标,如235813存储为 d[0]~d[5] 分别为 318532
int len; //记录长度
bign(){ //构造函数,方便初始化
memset(d,0,sizeof(d));
len = 0;
}
};
bign change(char str[]){ //读入字符数组,转化为大整数bign
bign a;
a.len = strlen(str);
for(int i = 0; i < a.len; i++){
a.d[i] = str[a.len - 1 - i] - '0';
}
return a;
}
int compare(bign a, bign b){ //比较大整数a与b的大小。 a>b,a=b,a<b 分别返回 1,0,-1
if(a.len > b.len) return 1;
else if(a.len < b.len) return -1;
else{
for(int i = a.len - 1; i >= 0; i--){
if(a.d[i] > b.d[i]) return 1;
else if(a.d[i] < b.d[i]) return -1;
}
return 0;
}
}
void print(bign a){ //输出大整数
for(int i = a.len - 1; i >= 0; i--){
printf("%d", a.d[i]);
}
}
bign add(bign a, bign b){ //大整数加法
bign c;
int carry = 0;
for(int i = 0; i < a.len || i < b.len; i++){
int temp = a.d[i] + b.d[i] + carry;
c.d[c.len++] = temp % 10;
carry = temp / 10;
}
if(carry != 0){
c.d[c.len++] = carry;
}
return c;
}
bign sub(bign a, bign b){ //大整数减法a-b
bign c;
for(int i = 0; i < a.len || i < b.len; i++){
if(a.d[i] < b.d[i]){
a.d[i + 1]--;
a.d[i] += 10;
}
c.d[c.len ++] = a.d[i] - b.d[i];
}
while(c.len - 1 >= 1 && c.d[c.len - 1] == 0){
c.len--;
}
return c;
}
bign multi(bign a, int b){ //大整数 乘以 普通整数
bign c;
int carry = 0;
for(int i = 0; i < a.len; i++){
int temp = a.d[i] * b + carry;
c.d[c.len++] = temp % 10;
carry = temp / 10;
}
while(carry != 0){
c.d[c.len++] = carry % 10;
carry /= 10;
}
return c;
}
2、最大公约数与最小公倍数
int gcd(int a, int b){ //求a与b的最大公约数,a无须大于b
if(b == 0) return a;
else return gcd(b, a % b);
}
//a、b的最小公倍数 = a / 最大公约数 * b
3、分数运算
struct Fraction{ //用long long更好
int up, down; //规定1.分子up可以是负数,但分母down是非负数。2.若分数为0,规定up=0,down=1。3、分子分母互素(公约数只有1)
}
Fraction reduction(Fraction result){ //化简与约分,使一个分数满足上述三条规定
if(result.down < 0){ //若分母是负数,则分母变成非负数
result.up = -result.up;
result.down = -result.down;
}
if(result.up == 0){ //若分数为0,则分母置1
result.down = 1;
}else{ //约分,分子分母除以它们的最大公约数即可
int d = gcd(abs(result.up), result.down); //d为分子分母的最大公约数
result.up /= d;
result.down /= d;
}
return result;
}
void print(Fraction r){ //打印分数r
r = reduction(r);
if(r.down == 1){ //是整数(含0)
printf("%lld", r.up);
}
else if(abs(r.up) > r.down){ //是假分数
printf("%d %d/%d",r.up / r.down, abs(r.up) % r.down, r.down);
}
else{ //是真分数
printf("%d/%d", r.up, r.down);
}
}
Fraction add(Fraction f1, Fraction f2){ //分数相加,通分相加后再化简约分
Fraction res;
res.up = f1.up * f2.down + f2.up * f1.down;
res.down = f1.down * f2.down;
return reduction(res);
}
4、表达式计算
//题意:给定中缀表达式,计算值。
/*思路:1中缀转后缀:队列存后缀。顺序扫描中缀,若是操作数直接入队;
若是操作符op若优先级高则入栈,若优先级低或等于则弹栈至队列直到op优先级高于栈顶或栈空。扫描完后栈中剩余元素入队。
2计算后缀:队列顺序出队,若是操作数入栈,若是操作符弹栈两个数(后弹的是第一操作数),运算结果入栈。扫描完栈中只有一个数即是答案*/
struct node{
double num;
char op;
bool flag; //1表示是操作数,0表示是操作符
};
string str; //输入的表达式
stack<node> s; //操作符栈
queue<node> q; //后缀表达式
map<char,int> op;
//0、初始化(使用时请放main函数里)
op['+'] = op['-'] = 1;
op['*'] = op['/'] = 2;
for(string::iterator it = str.end(); it != str.begin(); it--){ //去掉str中所有空格
if(*it == ' ') str.erase(it);
}
void Change(){ //1、中缀转后缀
double num;
node temp;
for(int i = 0; i < str.size(); ){
if(str[i] >= '0' && str[i] <= '9'){ //若是操作数
temp.flag = true;
temp.num = str[i] - '0';
i++;
while(i < str.size() && str[i] >= '0' && str[i] <= '9'){
temp.num = temp.num * 10 + str[i] - '0';
i++;
}
q.push(temp);
}
else{ //若是操作符
temp.flag = false;
while(!s.empty() && op[str[i]] <= sp[s.top().op]){
q.push(s.top());
s.pop();
}
temp.op = str[i];
s.push(temp);
i++;
}
}
while(!s.empty()){ //剩余操作符入栈
q.push(s.top());
s.pop();
}
}
double Cal(){ //2、计算后缀,返回答案
double temp1, temp2;
node cur, temp;
while(!q.empty()){
cur = q.front();
q.pop();
if(cur.flag == true){ //若是操作数,直接入栈
s.push(cur);
}
else{ //若是操作符
temp2 = s.top().num; //第二操作数
s.pop();
temp1 = s.top().num; //第一操作数
s.pop();
temp.flag = true;
if(cur.op == '+') temp.num = temp1 + temp2;
else if(cur.op == '-') temp.num = temp1 - temp2;
else if(cur.op == '*') temp.num = temp1 * temp2;
else if(cur.op == '/') temp.num = temp1 / temp2;
s.push(temp);
}
}
return s.top().num;
}
5、KMP算法
文本串中匹配模式串 O(n+m)
// s[]是长文本,p[]是模式串,n是s的长度,m是p的长度,下标从1开始
//求模式串的Next数组:
for (int i = 2, j = 0; i <= m; i ++ )
{
while (j && p[i] != p[j + 1]) j = ne[j];
if (p[i] == p[j + 1]) j ++ ;
ne[i] = j;
}
// 匹配
for (int i = 1, j = 0; i <= n; i ++ )
{
while (j && s[i] != p[j + 1]) j = ne[j];
if (s[i] == p[j + 1]) j ++ ;
if (j == m)
{
j = ne[j];
// 匹配成功后的逻辑
}
}
6、快速幂
//求 m^k mod p,时间复杂度 O(logk)。
int qmi(int m, int k, int p)
{
int res = 1, t = m;
while (k)
{
if (k&1) res = res * t % p;
t = t * t % p;
k >>= 1;
}
return res;
}
7、判断质数
//判断x是不是质数,时间复杂度O(根号n)
bool is_prime(int x)
{
if (x < 2) return false;
for (int i = 2; i <= x / i; i ++ )
if (x % i == 0)
return false;
return true;
}
8、分解质因数
//形式:24 = 2 ^ 3 * 3 ^ 1,时间复杂度O(根号n)
void divide(int x)
{
for (int i = 2; i <= x / i; i ++ )
if (x % i == 0)
{
int s = 0;
while (x % i == 0) x /= i, s ++ ;
cout << i << ' ' << s << endl;
}
if (x > 1) cout << x << ' ' << 1 << endl;
cout << endl;
}