文章目录
排序
快速排序
int q[N];
void quick_sort(int q[],int l,int r){
if(l>=r)return;
int i=l-1,j=r+1,x=q[l+r>>1];
while(i<j){
do i++;while(q[i]<x);
do j--;while(q[j]>x);
if(i<j)swap(q[i],q[j]);
}
quick_sort(q,l,j),quick_sort(q,j+1,r);
}
归并排序
int q[N];
int temp[N];//临时数组
void merge_sort(int q[],int l,int r){
if(l >= r) return;
//确定分界点
int mid=l+r>>1;
//递归排序
merge_sort(q,l,mid);
merge_sort(q,mid+1,r);
//排序
int k=0,i=l,j=mid+1;
while(i<=mid&&j<=r){
if(q[i]<=q[j])temp[k++]=q[i++];
else temp[k++]=q[j++];
}
//归并
while(i<=mid)temp[k++]=q[i++];
while(j<=r)temp[k++]=q[j++];
for(i=l,j=0;i<=r;i++,j++)q[i]=temp[j];
}
//调用,数据从0开始储存。
merge_sort(q,0,n-1);
典题
【归并排序求逆序对】
同时逆序对还可以用树状数组的方式求。时间复杂度同样是 O ( n l o g n ) O(nlogn) O(nlogn)。
//只要在while里边加一行就行了
while(i<=mid&&j<=r){
if(a[i]<=a[j])b[k++]=a[i++];
else{
ans+=mid-i+1;
b[k++]=a[j++];
}
}
二分
整数二分
// 区间[l, r]被划分成[l, mid]和[mid + 1, r]时使用:求左边界。
int bsearch_1(int l,int r){
while(l<r){
int mid=l+r>>1;
if(check(mid))r=mid;//check()判断mid是否满足性质
else l=mid+1;
}
return l;
}
// 区间[l, r]被划分成[l, mid - 1]和[mid, r]时使用:求右边界。
int bsearch_2(int l,int r){
while(l<r){
int mid=l+r+1>>1;
if (check(mid))l=mid;
else r=mid-1;
}
return l;
}
实数二分
//固定循环次数
for(int i=0;i<100;i++){//求mid
double mid=(l+r)/2;
if(calc(mid))r=mid;
else l=mid;
}
//eps精度
const double eps=1e-8;//一般将精度定为要求的后两位。
double l=-10000,r=10000;
while(r-l>eps){
double mid=(l+r)/2;
if(check(mid))r=mid;
else l=mid;
}
典题
【连续边界】
有序数组中找某一数字的最大连续连续边界。
//找左边界
while(l<r){
int mid=l+r>>1;
if(a[mid]>=t)r=mid;
else l=mid+1;
}
//找右边界
while(l<r){
int mid=l+r+1>>1;
if(a[mid]<=t)l=mid;
else r=mid-1;
}
【n次方根】
使用实数二分实现,例求三次方根。
double l=-10000,r=10000;
while(r-l>1e-8){
double mid=(l+r)/2;
if(mid*mid*mid>=n)r=mid;
else l=mid;
}
高精度
将字符串转化为倒序数组。
for(int i=a.size()-1;i>=0;i--)A.push_back(a[i]-'0');
高精度加法
//C=A+B
vector<int> add(vector<int> &a,vector<int> &b){
vector<int>c;
int t=0;
for(int i=0;i<a.size()||i<b.size();i++){
if(i<a.size())t+=a[i];
if(i<b.size())t+=b[i];
c.push_back(t%10);
t/=10;
}
if(t)c.push_back(1);
return c;
}
高精度减法
//判断a和b的大小关系,直接用string的比较会出问题
bool cmp(vector<int> &A,vector<int> &B){
if(A.size()!=B.size())return A.size()>B.size();
for(int i=A.size()-1;i>=0;i--)
if(A[i]!=B[i])
return A[i]>B[i];
return true;
}
//C=A-B
vector<int> sub(vector<int> &A,vector<int> &B){
vector<int> C;
for(int i=0,t=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
return C;
}
高精度乘法
//C=A*B(B是小整数)
vector<int> mul(vector<int> &A,int b){
vector<int> C;
int t=0;
for (int i=0;i<A.size()||t;i++){
if(i<A.size())t+=A[i]*b;
C.push_back(t%10);
t/=10;
}
while(C.size()>1&&C.back()==0)C.pop_back();//去除前导0
return C;
}
高精度除法
//C=A/B(B为小整数);可求余数
vector<int> div(vector<int> &A,int b){//r是引用传递
vector<int> C;//商
int r=0;//余数
for (int i=A.size()-1;i>=0;i--){//从最高位开始算
r=r*10+A[i];
C.push_back(r/b);
r%=b;
}
reverse(C.begin(),C.end());
while(C.size()>1&&C.back()==0)C.pop_back();//去除前导0
return C;
}
前缀和
前缀和多用于频繁询问区间和的问题。
一维前缀和
//创建
s[i]=s[i-1]+a[i];
//查找
a[l~r]=s[r]-s[l-1];
二维前缀和
//创建
s[i][j]=s[i-1][j]+s[i][j-1]-s[i-1][j-1]+a[i];
//查找
s[x1~x2][y1~y2]=s[x2][y2]+s[x1-1][y1-1]-s[x1-1][ y2]-s[x2][y1-1];
三维前缀和
//创建
s[i][j][k]=s[i-1][j][k]+s[i][j-1][k]+s[i][j][k-1]+t
-s[i-1][j-1][k]-s[i-1][j][k-1]-s[i][j-1][k-1]
+s[i-1][j-1][k-1];
//查找
s[x1~x2][y1~y2][z1~z2]=s[x2][y2][z2]
-s[x2][y1][z2]-s[x1][y2][z2]-s[x2][y2][z1]
+s[x1][y1][z2]+s[x1][y2][z1]+s[x2][y1][z1]
-s[x1][y1][z1];
典题
【矩形包含】
给定 n n n 个矩形的长和宽,每次询问两个矩形的长宽,一小一大,求长宽的范围都在这两个矩形中的矩形数量。(重叠不算,不能旋转)
//使用二维前缀和统计,数组的坐标表示长和宽,记录一个矩形相当与在g[i][j]++,然后处理1000*1000的前缀和。
差分
差分多处理频繁对区间进行整体加减的问题。
一维差分
对序列 [ l , r ] [l,r] [l,r] 加上 c c c。
//构造一维差分
void insert(int l,int r,int c){
b[l]+=c;
b[r+1]-=c;
}
for(int i=1;i<=n;i++){
scanf("%d",&a[i]);
insert(i,i,a[i]);
}
//调用操作
while(m--){
int l,r,c;
scanf("%d%d%d",&l,&r,&c);
insert(l,r,c);
}
//前缀和
for(int i=1;i<=n;i++)b[i]+=b[i-1];
二维差分
//构造二维差分
void insert(int x1,int y1,int x2,int y2,int c){
b[x1][y1]+=c;
b[x2+1][y1]-=c;
b[x1][y2+1]-=c;
b[x2+1][y2+1]+=c;
}
for(int i=1;i<=n;i++)
for(int j=1;j<=m;j++){
scanf("%d",&a[i][j]);
insert(i,j,i,j,a[i][j]);
}
//调用操作
while(q--){
int x1,x2,y1,y2,c;
scanf("%d%d%d%d%d",&x1,&y1,&x2,&y2,&c);
insert(x1,y1,x2,y2,c);
}
//前缀和
for(int i=1;i<=n;i++)
for(int j=1;j<=m;j++)
b[i][j]+=b[i-1][j]+b[i][j-1]-b[i-1][j-1];
双指针
典题
最长不重复子序列(判断是否子序列)
for(int i=0,j=0;i<n;i++){
s[a[i]]++;
while(j<i&&s[a[i]]>1)s[a[j++]]--;
res=max(res,i-j+1);
}
求两个数组中相加等于 x 的数量(数对)
for(int i=0,j=m-1;i<n;i++){
while(j>=0&&a[i]+b[j]>x)j--;
if(j>=0&&a[i]+b[j]==x)cout<<i<<" "<<j<<endl;
}
位运算
快速幂
while(tmp){
if(tmp&1)sum++;
tmp>>=1;
}
典题
【a 的 b 次幂 mod p】
long long res=1;
while(b){
if(b&1)res=res*a%p;
a=a*a%p;
b>>=1;
}
【a 乘 b mod p】
用高精度不好求模。
long long res=0;
while(b){
if(b&1)res=(res+a)%p;
a=(a+a)%p;
b>>=1;
}
【第 k 位二进制】
if(x>>k&1)puts("1");
else puts("0");
【最后一位 1】
int lowbit(int x){return x&(-x);}
离散化
在线离散化,不用存下来,直接使用 map。
unordered_map<int,int>mp;
int get(int x){
if(!mp.count(x))mp[x]=++cnt;
return mp[x];
}
离线离散化,需要将数据存下来,并进行排序判重。
//去重(去重是一种双指针算法)
vector<int> alls;//存储所有待离散化的值
sort(alls.begin(),alls.end());//将所有的值排序
alls.erase(unique(alls.begin(),alls.end()),alls.end());//去掉重复元素
int find(int x){//找到第一个大于等于x的位置
int l=0,r=alls.size()-1;
while(l<r){
int mid=l+r>>1;
if(alls[mid]>=x)r=mid;
else l=mid+1;
}
return r+1;//映射到1,2,...(不加1从0开始)
}
区间合并
更像贪心。
【区间选点】
将每个区间按右端点从小到大排序。
二、从前往后依次枚举每个区间
- ①如果当前区间中已经包含点,则直接pass
- ②否则选择当前区间的右端点。
【区间合并】
这是一个贪心题,①按照区间左端点排序。②遍历。