我是可爱的小目录~
1.快速排序-4种
void quick_sort(int a[],int left,int right)
{
if(left>=right) return;
//四种写法,如果选用不合适的方法配对的话会导致无限划分
//-1边界left while循环可求l范围(left,right) r范围(left,right-1) 不能用l分界,任选下2
int l=left-1,r=right+1,x=a[(left+right)/2];
int l=left-1,r=right+1,x=a[left];
//-2边界right 通过while循环可求l范围(left+1,right)[猜] r范围(left,right)不能用r分界,任选下2
int l=left-1,r=right+1,x=a[(left+right+1)/2];
int l=left-1,r=right+1,x=a[right];
while(l<r)
{
do l++; while(a[l]<x);
do r--; while(a[r]>x);
if(l<=r) swap(a[l],a[r]);
}
//两种写法都满足将一个整体切成小于x和大于x两块
quick_sort(a,left,r);quick_sort(a,r+1,right);//-1 r分界
quick_sort(a,left,l-1);quick_sort(a,l,right);//-2 l分界
}
2.归并排序
void merge_sort(int q[],int l,int r)
{
if(l>=r) return;
int mid=l+(r-l)/2;
merge_sort(q,l,mid);
merge_sort(q,mid+1,r);
int temp[r-l+1],k=0,n=0,left=l,j=l,right=mid+1;
while(left<=mid&&right<=r)
{
if(q[left]<q[right]) temp[k++]=q[left++];
else temp[k++]=q[right++];
}
while(left<=mid) temp[k++]=q[left++];
while(right<=r) temp[k++]=q[right++];
while(n<k) q[j++]=temp[n++];
}
3.二分法-整数-2种
int l=0,r=n-1,mid;
while(l<r)
{
mid=(l+r)/2;
//获得满足条件最小值
if(check(mid)) r=mid;
else l=mid+1;
}
或
while(l<r)
{
mid=(l+r+1)/2;//不+1可能进入死循环
//获得满足条件最大值
if(check(mid)) l=mid;
else r=mid-1;
}
//退出时l,r相等 输出任意一个皆可
//if(check(l)) return l;查找成功
//return -1;查找失败
3.二分法-小数-2种
double l=xxx,r=xxx,mid;//根据题意选择合适端点
while(r-l>=1e-8)//最好比题目要求精度多两位6+2=8
{
mid=(l+r)/2;
//获得满足条件最小值
if (check(mid)) r=mid;
else l=mid;//不用+1
}
或
while(r-l>=1e-8)//最好比题目要求精度多两位6+2=8
{
mid=(l+r)/2;//不用+1,因为是浮点数,几乎很难出现死循环的情况
//获得满足条件最大值
if (check(mid)) l=mid;
else r=mid;//不用-1
}
//退出时l,r,mid近似相等,输出任意一个皆可
//if(check(mid)) return l;查找成功
//return -1;查找失败
4.高精度计算-加法-不压位
int t=0;//在此之前需要处理使得左边数据大于右边
for(int i=0;i<a.size();i++)
{
t+=a[i];
if(i<b.size()) t+=b[i];
c.push_back(t%10);//注意不要写反%和/
t=t/10;
}
if(t) c.push_back(1);
4.高精度计算-加法-压位-待补充
************
4.高精度计算-减法
int t=0;//在此之前需要处理使得左边数据大于右边
for(int i=0;i<a.size();i++)
{
t=a[i]-t;
if(i<b.size()) t-=b[i];
c.push_back((t+10)%10);
if(t<0) t=1;//是否借位
else t=0;
}
while(c.size()>1&&c.back()==0) c.pop_back();//删除前导0
4.高精度计算-乘法
int t=0;//被乘数a,乘数b
for(int i=0;i<a.size()||t;i++)
{
if(i<a.size()) t+=a[i]*b;//跟加法非常类似
c.push_back(t%10);
t=t/10;
}
while(c.size()>1&&c.back()==0) c.pop_back();//删除前导0
4.高精度计算-除法
int t=0;//被除数a,除数b
for(int i=a.size()-1;i>=0;i--)//与加减乘相反,从高位开始处理
{
t=t*10+a[i];
c.push_back(t/b);
t%= b;
}
reverse(c.begin(),c.end());//没有pop_front,必须逆序后处理前导0
while(c.size()>1&&c.back()==0) c.pop_back();
5.前缀和<—逆向—>差分-一维
void insert(int l,int r,int c)//对差分进行处理的函数
{
b[l]+=c;
b[r+1]-=c;
}
insert(i,i,x);//b[i]存入的就是此时的差分
insert(l1,l2);//对某一段差分进行处理
b[i]+=b[i-1];//前缀和计算原本的数据
5.前缀和<—逆向—>差分-二维
void insert(int l1,int r1,int l2,int r2,int c)//对差分进行处理的函数
{
b[l1][r1]+=c;
b[l1][r2+1]-=c;
b[l2+1][r1]-=c;
b[l2+1][r2+1]+=c;
}
insert(i,j,i,j,x);//b[i][j]存入的就是此时的差分
insert(l1,r1,l2,r2,x);//对某一段差分进行处理
b[i][j]+=b[i-1][j]+b[i][j-1]-b[i-1][j-1];//前缀和计算原本的数据
6.双指针
根据条件设置
7.位运算
n>>k&1//n的第k位数字
n&(n-1)//n的最后一位为1的数字
8.离散化
typedef pair<int,int> PII;
vector<PII> add;
vector<int> all;//实际需要操作的坐标点
int a[xxx];
int find(int x)//二分查找映射到all的坐标
sort(all.begin(), all.end());
all.erase(unique(all.begin(), all.end()), all.end());
//在映射后数组处理,如此处加
for(auto item:add)
{
int x = find(item.first);
a[x] += item.second;
}
9.区间合并
typedef pair<int,int> PII;
void merge(vector<PII> &segs)
{
vector<PII> res;
sort(segs.begin(),segs.end());//先以first再以second进行sort
int st=-2e9,ed=-2e9;//都选择最左边的部分
for(auto seg:segs)
if(ed<seg.first)//无交叉
{
if(st!=-2e9) res.push_back({st,ed});//第一个可能后续还有能合并的区间不能直接插入
st=seg.first,ed=seg.second;
}
else ed=max(ed,seg.second);//有交叉
if (st!=-2e9) res.push_back({st,ed});//最后一个区间可能还因为有交叉没有加入
segs=res;
}
10.单链表(数组模拟)
void init()//初始化
{
head=-1;//开始只有末尾的空节点,不像双指针有左右不使用节点
idex=0;//将要插入的节点的数组下标
}
void head_insert(int x)//每一个新节点都在头
{
e[idex]=x;
ne[idex]=head;
head=idex++;
}
void del(int k)//删除插入的第k个节点,因为idex此处为0,调用时需要-1
{
ne[k]=ne[ne[k]];
}
void insert(int k,int x)//插入在第k个节点后,因为idex此处为0,调用时需要-1
{
e[idex]=x;
ne[idex]=ne[k];
ne[k]=idex++;
}
for(int i=head;i!=-1;i=ne[i]) printf("%d ",e[i]);//并不受下标顺序影响
10.双链表(数组模拟)
void insert(int a, int x)
{
e[idx]=x;
l[idx]=a,r[idx]=r[a];
l[r[a]]=idx,r[a]=idx++;
}
// 删除节点a
void remove(int a)
{
l[r[a]]=l[a];
r[l[a]]=r[a];
}
11.单调栈(数组模拟)
-----------------------------------对于表达式计算----------------------------------------
1.数字时直接插入数字栈
2.左括号时直接插入符号栈
3.右括号时数字和字符从栈中弹出进行计算后在放入数字栈[最后的左括号也需要出栈]
4.其他符号若比栈中的优先级小,数字栈弹出数字处理,直到无字符或者出现左括号停止,否则入栈
5.表达式遍历完后要查看符号栈是否还有没有使用的符号,也需要进行处理
---------------------------------------------------------------------------------------
//左边最近比它小 左边最近比它大a[index]<=x 右边的两种情况逆序两次
while(index&&a[index]>=x) index--;//a数组表示已经插入,x表示准备插入
if(!index) printf("-1 ");
else printf("%d ",a[index]);
a[++index]=x;//原来减多了需要补回来
12.单调队列(数组模拟)
int h[N],a[N];//存储下标和存储数值
for(int i=0;i<n;i++)
{
//以下两个while都在元素非空的情况下
//保持宽度为k
while(front<=rear&&k<i-h[front]+1) front++;
//保持元素递增,此时的元素必进队列,队列里不满足条件的出队
while(front<=rear&&a[h[rear]]>=a[i]) rear--;
h[++rear]=i;
//从滑动窗口宽度够的时候开始就要输出值,由于递增最左边是最小值
if(i+1>=k) printf("%d ",a[h[front]]);
}
//保持元素递增获得的滑动窗口最右[最大值]和保持元素递减获得的滑动窗口最左[最大值]并不相同
13.KMP
//p为模式串,n为长度 s为匹配串,m为长度
for(int j=0,i=1;i<n;i++)
{
while(j&&p[i]!=p[j]) j=ne[j-1];//回到相同0~j-1跟此时
if(p[i]==p[j]) j++;
ne[i]=j;
}
for(int j=0,i=0;i<m;i++)
{
while(j&&s[i]!=p[j]) j=ne[j-1];
if(s[i]==p[j]) j++;
if(j==n)
{
printf("%d ",i-n+1);
j=ne[j-1];
}
}
14.字典树(数组模拟)
//高效存储和查找字符串集合,关键在于存储
int p=0;
//从高位向低位存储,因为必须尽可能在前面的数字是相反的
for(xxx)根据条件将每一位存入数组
{
int n=x>>i&1;
if(!c[p][n]) c[p][n]=++idex;
p=c[p][n];//左边位存储需要的长度 右边位存储可能取的值
}
15.并查集
int find(int x)//找祖宗
{
if(x!=s[x]) s[x]=find(s[x]);//x目前不是祖宗,把自己的值赋为祖宗
return s[x];
}
void merge(int a,int b)//已在同一个不需要合并
{
int x=find(a);//一定要上升到祖宗而不是自己
int y=find(b);
s[x]=y;
siz[y]+=siz[x];
}
16.堆(数组模拟)
//此处除了可能父子比较还可能同级比较 不能直接a[x]和a[2*x]
void down(int x)//对x下标的值进行处理
{
int j=x;
if(2*x<=siz&&a[j]>a[2*x]) j=2*x;
if(2*x+1<=siz&&a[j]>a[2*x+1]) j=2*x+1;
if(j!=x)
{
swap(a[j],a[x]);
down(j);
}
}
void up(int x)//对x下标的值进行处理
{
while(x/2&&a[x]<a[x/2])
{
swap(a[x],a[x/2]);
x=x/2;
}
}
for(int i=n/2;i;i--) down(i);//形成最开始的小根堆
while(m--)
{
a[1]=a[siz--];//输出第一个后把第一个和最后一个进行交换
down(1);//其他都已经有序
}
//若非顶点进行删除,先将该点与最后一个点交换,再up(k); down(k);
17.哈希(数组模拟)
//开放寻址法
const int N=200003,null=0x3f3f3f3f;//合适的长度和为空的占位
int a[N];//存储映射的
int find(int x)//把返回值写成bool,应该是返回下标而不是返回能不能找到
{
int t=(x%N+N)%N;
while(a[t]!=null&&a[t]!=x)//循环处理
{
t++;
if(t==N)
t=0;
}
return t;
}
memset(a, 0x3f, sizeof a);//按位填充数组
a[find(x)]=x;
或
//拉链法
void insert(int x)//形成头插法链表 递减不一定连续下标
{
int t=(x%N+N)%N;
e[idex]=x;
ne[idex]=a[t];
a[t]=idex++;
}
int find(int x)//把返回值写成bool,应该是返回下标而不是返回能不能找到
{
int t=(x%N+N)%N;
for (int i = a[t]; i != -1; i = ne[i])
if (e[i] == x)
return true;
return false;
}
memset(a,-1, sizeof a);//按位填充数组
//哈希的应用-字符串哈希
const int P=1331;//为1331以此方法发生冲突可能性比较低
cout<<c[r]-c[l-1]*p[r-l+1];//要右边加起来也为r
for(int i=1;i<=n;i++)
{
c[i]=P*c[i-1]+str[i-1];//和y总的读入不同
p[i]=P*p[i-1];
}
18.DFS
void dfs(int x)
{
//根据题意设置终点状态
for(int i=1;i<=n;i++)
{
if(!b[i])
{
a[x]=i;//修改
b[i]=true;//标记
dfs(x+1);
b[i]=false;//还原标记
}
}
}
19.BFS
void bfs()
{
queue<PII> s;//总是把队列和栈拼写写反
s.push({1,1});
memset(dis,-1,sizeof dis);
dis[1][1]=0;
int dx[4]={1,0,-1,0};
int dy[4]={0,1,0,-1};
while(s.size())
{
PII s1=s.front();
s.pop();
for (int i=0;i<4;i++)
{
int x=s1.first+dx[i];
int y=s1.second+dy[i];
if (x>=1&&x<=n&&y>=1&&y<=m&&mg[x][y]==0&&dis[x][y]==-1)
{
dis[x][y]=dis[s1.first][s1.second]+1;
s.push({x,y});
}
}
}
待定.质数、质因数、欧拉函数
//质数-试除法
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;
}
或
//质数-朴素筛
void is_prime(int n)
{
for(int i=2;i<=n;i++)//注意=n
{
if(!st[i])
{
cnt++;//}也可以移动到这里
for(int j=i;j<=n;j+=i) st[j]=true;
}
}
}
或
//质数-线性筛- [欧拉函数]https://www.acwing.com/solution/content/28507/
void is_prime(int n)
{
//ol[1]=1;存储某个数的欧拉函数
for(int i=2;i<=n;i++)
{
if(!st[i])
{
prime[cnt++] = i;
//ol[i]=i-1;质数前的1-n个数都与自己是互质的
}
for(int j = 0; i*prime[j]<=n;j++)//方便看,把i移动
{
st[i*prime[j]]=true;//质数*合数一定是合数
//i>prime[j] 且合数i*prime[j]中prime[j]是最小素因子
if(i%prime[j]==0)
{
//ol[i*prime[j]]=ol[i]*prime[j];
break;
}
//ol[i*prime[j]]=ol[i]*(prime[j]-1);
}
}
}
//质因数-试除法
void divide(int x)
{
for(int i=2;i<=x/i;i++)
while(x%i==0) x=x/i;//计算欧拉函数ol=ol/i*(i-1);
if(x>1) //输出x 计算欧拉函数ol=ol/x*(x-1);
}
待定.因数
//求因数
vector<int> get_divisors(int x)
{
vector<int> res;
for(int i=1;i<=x/i;i++)
{
if(x%i==0)
{
res.push_back(i);
if(i!=x/i) res.push_back(x/i);
}
}
sort(res.begin(), res.end());
return res;
}
//通过unordered_map存入每个数值出现的次数
long long sum=1;//注意这块需要设置为long long
//因数个数
for(auto item:m)
sum=sum*(item.second+1)%N;
//因数之和
for(auto item:m)
{
long long s=1;//注意这块也需要设置为long long
while(item.second--) s=(s*item.first+1)%N;
sum=s*sum%N;
}
//欧几里得
int gcd(int a, int b)
{
return a ?gcd(b%a,a): b;//a?--左边考虑为0--左边b%a--右边a
return b ?gcd(b,a%b): a;//b?--右边考虑为0--左边a%b--左边b
}
//扩展欧几里得
int kz_gcd(int a,int b,int &x,int &y)//通过引用一直得到实时的x,y
{
if(b==0)
{
x=1,y=0;
return a;
}
int x1,y1;
int gcd=kz_gcd(b,a%b,x1,y1);
x=y1,y=x1-a/b*y1;//y1不要随便换位置,可能有一定误差
//过程中除了a不关心x1、y1、gcd的变换 因为xy最后还是会变成1 0
return 0;
}
待定.快速幂
void ksm(int a,int b,int p)
{
long long m=1;
long aa=a;//由于a乘多次可能超限
while(b)
{
if(b&1) m=m*aa%p;
b=b>>1;
aa=aa*aa%p;//每一项都是先模再乘
}
cout<<m<<endl;
}
//根据费马小定理 快速幂求逆元需要满足a不是p的倍数 p是质数
if(a%p!=0) ksm(a,p-2,p);