排序
快速排序
快排属于分治算法,分治算法都有三步:
- 分成子问题
- 递归处理子问题
- 子问题合并
```cpp
include
include
using namespace std; void quickSort(vector& q,int l,int r){ if(l>=r) return; int i=l-1,j=r+1,x=q[(l+r)>>1]; while(i<j){
} quickSort(q,l,j);//quick(q,l,i-1); quickSort(q,j+1,r);//quick(q,i,r); } int main(){ int n; cin>>n; vectordo i++;while(q[i]<x);
do j--;while(q[j]>x);
if(i<j) swap(q[i],q[j]);
q(n,0); for(int i=0;i<n;i++){
} quickSort(q,0,n-1); for(auto num:q){cin>>q[i];
} return 0; }cout<<num<<" ";
`while`循环结束后,`q[l..j] <= x`,`q[j+1..r] >= x`<br />注:`q[l..j] <= x`意为`q[l],q[l+1]...q[j-1],q[j]`的所有元素都`<= x`
<a name="GRcht"></a>
### 第k个数
快排序实际上分了三段:<br />一次迭代输出的是:
- 左边:小于等于x
- 右边:大于等于x,分界点在i/j
因此若是`i-l+1>需要的长度`,在前半段查<br />否则再后半段查,**因为后半段中,前面j前面部分确定已经小于此结果**,因此需要找<br />`j+1,r`中的第`k-(j-l+1)`个,即前面`j-l+1`个已经是小数了
```cpp
//这里填你的代码^^
//注意代码要放在两组三个点之间,才可以正确显示代码高亮哦~
#include<iostream>
using namespace std;
const int N=1000010;
int q[N];
int quickSort(int q[], int l, int r, int k){
if(l>=r) return q[l];
int i=l-1;
int j=r+1;
int 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]);
}
int left=j-l+1;
if(left>=k){
return quickSort(q,l,j,k);
}else{
return quickSort(q,j+1,r,k-(j-l+1));
}
}
int main(){
int n,k;
cin>>n>>k;
for(int i=0;i<n;i++) cin>>q[i];
cout<<quickSort(q,0,n-1,k);
return 0;
}
归并排序
- 选取中点
- 排序
- 合并
```cpp
include
using namespace std; const int N=100010; int q[N]; int temp[N]; void mergeSort(int q[],int l,int r){ if(l>=r) return; int mid=(l+r)>>1; mergeSort(q,l,mid); mergeSort(q,mid+1,r); int k=0,i=l,j=mid+1; while(i<=mid && j<=r){
} while(i<=mid) temp[k++] = q[i++]; while(j<=r) temp[k++] = q[j++]; for(int i=l,k=0;i<=r;i++,k++){if(q[i]<=q[j]) temp[k++]=q[i++];
else temp[k++]=q[j++];
} } int main(){ int n; cin>>n; for(int i=0;iq[i]=temp[k];
>q[i]; mergeSort(q,0,n-1); for(int i=0;i<n;i++) cout<<q[i]<<” “; return 0; }
<a name="Sn5Tl"></a>
### 逆序对的数量
当前逆序对数量=前半边逆序对数量+后半边逆序对数量+合并过程中产生的逆序对<br />计算合并过程中的逆序对:
- `if q[i]<=q[j]` , `(q[i], q[j]) `则不是逆序对。
- `else if(q[i]>q[j])` `(q[i], q[j])`是逆序对,并且`q[i]....q[mid]`与`q[j]` 形成 `mid-i+1`个逆序对。
```cpp
#include<iostream>
using namespace std;
const int N=100010;
int q[N];
int temp[N];
long long mergeSort(int q[],int l,int r){
if(l>=r) return 0;
int mid=(l+r)>>1;
long long cnt=0;
cnt+=mergeSort(q,l,mid);
cnt+=mergeSort(q,mid+1,r);
int i=l,j=mid+1,k=0;
while(i<=mid&&j<=r){
if(q[i]<=q[j]){
temp[k++]=q[i++];
}else{
temp[k++]=q[j++];
cnt+=mid-i+1;
}
}
while(i<=mid) temp[k++]=q[i++];
while(j<=r) temp[k++]=q[j++];
for(int i=l,k=0;i<=r;i++,k++){
q[i]=temp[k];
}
return cnt;
}
int main(){
int n;
cin>>n;
for(int i=0;i<n;i++) cin>>q[i];
cout<<mergeSort(q,0,n-1);
return 0;
}
二分查找
二分查找的核心不是有序性,而是边界,即存在边界能将数组分成两个区间,每段分别满足一个条件。
对于整数二分而言,可以对左边界和右边界进行》
两个二分查找的板子:
void binarySearch1(int l,int r){
while(l<r){
int mid=(l+r)>>1;
if(check(mid)) r=mid;
else l=mid+1;
}
}
//求右边界的时候
void binarySerch2(int l,int r){
while(l<r){
int mid=(l+r+1)>>1;
if(check(mid)) l=mid;
else r=mid-1;
}
}
:::info 注意的点是当l=mid时需要向上取整,因为C++默认是向下取整的。 :::
数的范围
#include<iostream>
using namespace std;
const int N=100010;
int vec[N];
int main(){
int n;
cin>>n;
int q;
cin>>q;
for(int i=0;i<n;i++){
cin>>vec[i];
}
while(q--){
int k;
cin>>k;
int l=0,r=n-1;
while(l<r){
int mid=(l+r)>>1;
if(vec[mid]>=k) r=mid;
else l=mid+1;
}
if(vec[l]!=k){
cout<<"-1 -1"<<endl;
}else{
cout<<l<<" ";
int l=0,r=n-1;
while(l<r){
int mid=(l+r+1)>>1;
if(vec[mid]<=k) l=mid;
else r=mid-1;
}
cout<<l<<endl;
}
}
return 0;
}
数的三次方跟
#include<iostream>
#include<cstdio>
using namespace std;
int main(){
double l=-10000;
double x;
cin>>x;
double r=10000;
while(r-l>1e-9){
double mid=(l+r)/2;
if(mid*mid*mid<x) l=mid;
else r=mid;
}
printf("%.6f", l);
return 0;
}
前缀和与差分
前缀和
数列:
前缀和:
- 如何求
递推:
for(i=1;i<=n;i++){
S(i)=S(i-1)+a(i);
}
- 作用:快速求一段数组区间的和
二维前缀和
如何求红色部分面积?
差分
差分实际上相当于前缀和的逆运算
数列:
构造:使得,b即称为a的差分,a即使b的前缀和
作用:
在[l,r]
区间内,所有元素+c
用差分来做可以做到;
只要用,即可。 :::info 关于构造问题:
一开始可以认为a数组全是0,但是进行了n次插入,即第一次在[1,1]区间插入了a_1,对应操作是b1+a_1,b_2-a;以此类推。 :::
双指针算法
:::info 双指针算法的核心思想是运用某些性质降低复杂度: ::: 基本的板子
for(int i=0,j=0;i<n;i++){
j=i;
while(j<n&&check(j))...
}
位运算
查看n
的二进制表示中第k
位是几;
:::warning
- 先把第k位移动到最右边:
n>>k
- 看个位是几:
x&1
:::
lowbit
:::info
返回x的最后一位1x=1010
lobit(x)=10
x=101000
lobit(x)=1000
:::
lobit(x)=x&-x
C++中负数是通过取反原数+1来实现的(补码)
:::warning
x=0010
-x=1110
x&-x=0010
:::
离散化
:::warning
Definition:
例如有一系列数,值域在,但是个数比较少,有时候可能需要用这些值作为下标来做,不能直接开辟一个如此巨大的数组,因此需要将这些数字映射到一个更小的值域区间。
:::
:::info
e.g.
a[]: 1,3,100,2000,50000
b[]: 0,1,2,3,4 映射后的数组
:::
可能的问题:
- a中可能有重复元素—需要去重
- 希望快速映射:如何算出x在a中的映射值?:因为a是有序的,可以用二分法
去重的操作:sort(alls.begin(),alls.end());
alls.erase(unique(alls.begin(), alls.end()), alls.end()); // 去掉重复元素
// 二分求出x对应的离散化的值
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, ...n
}
区间和
例子是求区间和
using namespace std;
using PII=pair
vector
int find(int x){
//查找alls中小于或等于x的最大值的坐标
int l=0,r=alls.size()-1;
while(l
sort(alls.begin(),alls.end());
alls.erase(unique(alls.begin(),alls.end()),alls.end());
//离散化=排序+去重
for(auto item:add){
//插入
int x=find(item.first);
a[x]+=item.second;
}
for(int i=1;i<=alls.size()+1;i++){
// 算前缀和
s[i]=s[i-1]+a[i];
}
for(auto item:query){
//查询
int l=find(item.first),r=find(item.second);
cout<<s[r]-s[l-1]<<endl;
//前缀和求区间公式
}
return 0;
}
<a name="TWB2K"></a>
## 区间合并
![FIG.svg](https://cdn.nlark.com/yuque/0/2022/svg/1303425/1656255144934-fee1be85-74d8-42ac-8717-205db8388b39.svg#clientId=u91b5a77c-c598-4&crop=0&crop=0&crop=1&crop=1&from=ui&id=u5479da23&margin=%5Bobject%20Object%5D&name=FIG.svg&originHeight=61&originWidth=351&originalType=binary&ratio=1&rotation=0&showTitle=false&size=1764&status=done&style=none&taskId=u14560581-25cb-4cb1-8eb8-5b192a043f4&title=)<br />区间合并:快速地将有交集的区间进行合并
:::info
边界情况:只有端点相交也算是相交
:::
1. 按照区间左端点排序
1. 扫描整个区间,将可能有交集的区间合并
1. 扫描过程中维护一个当前的区间
1. 三种情况考虑
1. 在当前区间内,不变
1. 末尾超出了,更新区间范围
1. 无交集,此区间更换为当前的区间,原来区间存入
![FIG.svg](https://cdn.nlark.com/yuque/0/2022/svg/1303425/1656255545053-f4e33b62-c0f2-43dd-a8a7-c3e156834084.svg#clientId=u91b5a77c-c598-4&crop=0&crop=0&crop=1&crop=1&from=ui&id=u37d26b1d&margin=%5Bobject%20Object%5D&name=FIG.svg&originHeight=266&originWidth=881&originalType=binary&ratio=1&rotation=0&showTitle=false&size=11292&status=done&style=none&taskId=u751c7544-b347-410f-a857-d18b5b68bf9&title=)
```cpp
#include<iostream>
#include<vector>
#include<algorithm>
using namespace std;
using PII=pair<int,int>;
vector<PII> seg;
void merge(vector<PII>& segs){
vector<PII> res;
/*auto cmp=[](const PII a, const PII b){
return a.first>b.first;
//pair排序默认先拍左端点
};*/
sort(segs.begin(),segs.end());
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});//防止只有一个区间的情况,and将最后一个区间更新进去
segs=res;
}
int main(){
int n;
cin>>n;
for(int i=0;i<n;i++){
int l,r;
cin>>l>>r;
seg.push_back(make_pair(l,r));
}
merge(seg);
cout<<seg.size()<<endl;
return 0;
}