排序

快速排序

快排属于分治算法,分治算法都有三步:

  1. 分成子问题
  2. 递归处理子问题
  3. 子问题合并 ```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){
    1. do i++;while(q[i]<x);
    2. do j--;while(q[j]>x);
    3. if(i<j) swap(q[i],q[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; vector q(n,0); for(int i=0;i<n;i++){
    1. cin>>q[i];
    } quickSort(q,0,n-1); for(auto num:q){
    1. cout<<num<<" ";
    } return 0; }
  1. `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`
  2. <a name="GRcht"></a>
  3. ### 第k个数
  4. 快排序实际上分了三段:<br />一次迭代输出的是:
  5. - 左边:小于等于x
  6. - 右边:大于等于x,分界点在i/j
  7. 因此若是`i-l+1>需要的长度`,在前半段查<br />否则再后半段查,**因为后半段中,前面j前面部分确定已经小于此结果**,因此需要找<br />`j+1,r`中的第`k-(j-l+1)`个,即前面`j-l+1`个已经是小数了
  8. ```cpp
  9. //这里填你的代码^^
  10. //注意代码要放在两组三个点之间,才可以正确显示代码高亮哦~
  11. #include<iostream>
  12. using namespace std;
  13. const int N=1000010;
  14. int q[N];
  15. int quickSort(int q[], int l, int r, int k){
  16. if(l>=r) return q[l];
  17. int i=l-1;
  18. int j=r+1;
  19. int x=q[(l+r)>>1];
  20. while(i<j){
  21. do i++; while(q[i]<x);
  22. do j--; while(q[j]>x);
  23. if(i<j) swap(q[i],q[j]);
  24. }
  25. int left=j-l+1;
  26. if(left>=k){
  27. return quickSort(q,l,j,k);
  28. }else{
  29. return quickSort(q,j+1,r,k-(j-l+1));
  30. }
  31. }
  32. int main(){
  33. int n,k;
  34. cin>>n>>k;
  35. for(int i=0;i<n;i++) cin>>q[i];
  36. cout<<quickSort(q,0,n-1,k);
  37. return 0;
  38. }

归并排序

  1. 选取中点
  2. 排序
  3. 合并 ```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){
    1. if(q[i]<=q[j]) temp[k++]=q[i++];
    2. else temp[k++]=q[j++];
    } while(i<=mid) temp[k++] = q[i++]; while(j<=r) temp[k++] = q[j++]; for(int i=l,k=0;i<=r;i++,k++){
    1. q[i]=temp[k];
    } } int main(){ int n; cin>>n; for(int i=0;i>q[i]; mergeSort(q,0,n-1); for(int i=0;i<n;i++) cout<<q[i]<<” “; return 0; }
  1. <a name="Sn5Tl"></a>
  2. ### 逆序对的数量
  3. 当前逆序对数量=前半边逆序对数量+后半边逆序对数量+合并过程中产生的逆序对<br />计算合并过程中的逆序对:
  4. - `if q[i]<=q[j]` , `(q[i], q[j]) `则不是逆序对。
  5. - `else if(q[i]>q[j])` `(q[i], q[j])`是逆序对,并且`q[i]....q[mid]`与`q[j]` 形成 `mid-i+1`个逆序对。
  6. ```cpp
  7. #include<iostream>
  8. using namespace std;
  9. const int N=100010;
  10. int q[N];
  11. int temp[N];
  12. long long mergeSort(int q[],int l,int r){
  13. if(l>=r) return 0;
  14. int mid=(l+r)>>1;
  15. long long cnt=0;
  16. cnt+=mergeSort(q,l,mid);
  17. cnt+=mergeSort(q,mid+1,r);
  18. int i=l,j=mid+1,k=0;
  19. while(i<=mid&&j<=r){
  20. if(q[i]<=q[j]){
  21. temp[k++]=q[i++];
  22. }else{
  23. temp[k++]=q[j++];
  24. cnt+=mid-i+1;
  25. }
  26. }
  27. while(i<=mid) temp[k++]=q[i++];
  28. while(j<=r) temp[k++]=q[j++];
  29. for(int i=l,k=0;i<=r;i++,k++){
  30. q[i]=temp[k];
  31. }
  32. return cnt;
  33. }
  34. int main(){
  35. int n;
  36. cin>>n;
  37. for(int i=0;i<n;i++) cin>>q[i];
  38. cout<<mergeSort(q,0,n-1);
  39. return 0;
  40. }

二分查找

二分查找的核心不是有序性,而是边界,即存在边界能将数组分成两个区间,每段分别满足一个条件。
对于整数二分而言,可以对左边界和右边界进行》
fig0301.svg
两个二分查找的板子:

  1. void binarySearch1(int l,int r){
  2. while(l<r){
  3. int mid=(l+r)>>1;
  4. if(check(mid)) r=mid;
  5. else l=mid+1;
  6. }
  7. }
  8. //求右边界的时候
  9. void binarySerch2(int l,int r){
  10. while(l<r){
  11. int mid=(l+r+1)>>1;
  12. if(check(mid)) l=mid;
  13. else r=mid-1;
  14. }
  15. }

:::info 注意的点是当l=mid时需要向上取整,因为C++默认是向下取整的。 :::

数的范围

  1. #include<iostream>
  2. using namespace std;
  3. const int N=100010;
  4. int vec[N];
  5. int main(){
  6. int n;
  7. cin>>n;
  8. int q;
  9. cin>>q;
  10. for(int i=0;i<n;i++){
  11. cin>>vec[i];
  12. }
  13. while(q--){
  14. int k;
  15. cin>>k;
  16. int l=0,r=n-1;
  17. while(l<r){
  18. int mid=(l+r)>>1;
  19. if(vec[mid]>=k) r=mid;
  20. else l=mid+1;
  21. }
  22. if(vec[l]!=k){
  23. cout<<"-1 -1"<<endl;
  24. }else{
  25. cout<<l<<" ";
  26. int l=0,r=n-1;
  27. while(l<r){
  28. int mid=(l+r+1)>>1;
  29. if(vec[mid]<=k) l=mid;
  30. else r=mid-1;
  31. }
  32. cout<<l<<endl;
  33. }
  34. }
  35. return 0;
  36. }

数的三次方跟

  1. #include<iostream>
  2. #include<cstdio>
  3. using namespace std;
  4. int main(){
  5. double l=-10000;
  6. double x;
  7. cin>>x;
  8. double r=10000;
  9. while(r-l>1e-9){
  10. double mid=(l+r)/2;
  11. if(mid*mid*mid<x) l=mid;
  12. else r=mid;
  13. }
  14. printf("%.6f", l);
  15. return 0;
  16. }

前缀和与差分

前缀和

数列:第一讲 基础算法 - 图2
前缀和:第一讲 基础算法 - 图3

  1. 如何求第一讲 基础算法 - 图4

递推:

  1. for(i=1;i<=n;i++){
  2. S(i)=S(i-1)+a(i);
  3. }
  1. 作用:快速求一段数组区间的和

    二维前缀和

    fig0301.svg
    如何求红色部分面积?
    第一讲 基础算法 - 图6

    差分

    差分实际上相当于前缀和的逆运算
    数列:第一讲 基础算法 - 图7
    构造:第一讲 基础算法 - 图8使得第一讲 基础算法 - 图9,b即称为a的差分,a即使b的前缀和
    作用:
    [l,r]区间内,所有元素+c
    用差分来做可以做到第一讲 基础算法 - 图10;
    只要用第一讲 基础算法 - 图11第一讲 基础算法 - 图12即可。 :::info 关于构造问题:
    一开始可以认为a数组全是0,但是进行了n次插入,即第一次在[1,1]区间插入了a_1,对应操作是b1+a_1,b_2-a;以此类推。 :::

双指针算法

:::info 双指针算法的核心思想是运用某些性质降低复杂度:第一讲 基础算法 - 图13 ::: 基本的板子

  1. for(int i=0,j=0;i<n;i++){
  2. j=i;
  3. while(j<n&&check(j))...
  4. }

位运算

查看n的二进制表示中第k位是几; :::warning

  1. 先把第k位移动到最右边: n>>k
  2. 看个位是几: x&1 :::

lowbit

:::info 返回x的最后一位1
x=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:
例如有一系列数,值域在第一讲 基础算法 - 图14,但是个数比较少,有时候可能需要用这些值作为下标来做,不能直接开辟一个如此巨大的数组,因此需要将这些数字映射到一个更小的值域区间。 ::: :::info e.g.
a[]: 1,3,100,2000,50000
b[]: 0,1,2,3,4 映射后的数组 ::: 可能的问题:

  1. a中可能有重复元素—需要去重
  2. 希望快速映射:如何算出x在a中的映射值?:因为a是有序的,可以用二分法

去重的操作:
sort(alls.begin(),alls.end());
alls.erase(unique(alls.begin(), alls.end()), alls.end()); // 去掉重复元素

  1. // 二分求出x对应的离散化的值
  2. int find(int x) // 找到第一个大于等于x的位置
  3. {
  4. int l = 0, r = alls.size() - 1;
  5. while (l < r)
  6. {
  7. int mid = l + r >> 1;
  8. if (alls[mid] >= x) r = mid;
  9. else l = mid + 1;
  10. }
  11. return r + 1; // 映射到1, 2, ...n
  12. }

区间和

例子是求区间和

  1. 离散化要求的点(插入的点+查询的点)
  2. 前缀和 ```cpp

    include

    include

    include

using namespace std; using PII=pair; const int N=300010; int a[N],s[N];//前缀和

vector alls; vector add,query;

int find(int x){ //查找alls中小于或等于x的最大值的坐标 int l=0,r=alls.size()-1; while(l>1; if(alls[mid]>=x) r=mid; else l=mid+1; } return l+1; } int main(){ int n,m; cin>>n>>m; for(int i=0;i>x>>c; add.push_back({x,c}); alls.push_back(x); } for(int i=0;i>l>>r; query.push_back({l,r}); alls.push_back(l); alls.push_back(r); }

  1. sort(alls.begin(),alls.end());
  2. alls.erase(unique(alls.begin(),alls.end()),alls.end());
  3. //离散化=排序+去重
  4. for(auto item:add){
  5. //插入
  6. int x=find(item.first);
  7. a[x]+=item.second;
  8. }
  9. for(int i=1;i<=alls.size()+1;i++){
  10. // 算前缀和
  11. s[i]=s[i-1]+a[i];
  12. }
  13. for(auto item:query){
  14. //查询
  15. int l=find(item.first),r=find(item.second);
  16. cout<<s[r]-s[l-1]<<endl;
  17. //前缀和求区间公式
  18. }
  19. return 0;

}

  1. <a name="TWB2K"></a>
  2. ## 区间合并
  3. ![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 />区间合并:快速地将有交集的区间进行合并
  4. :::info
  5. 边界情况:只有端点相交也算是相交
  6. :::
  7. 1. 按照区间左端点排序
  8. 1. 扫描整个区间,将可能有交集的区间合并
  9. 1. 扫描过程中维护一个当前的区间
  10. 1. 三种情况考虑
  11. 1. 在当前区间内,不变
  12. 1. 末尾超出了,更新区间范围
  13. 1. 无交集,此区间更换为当前的区间,原来区间存入
  14. ![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=)
  15. ```cpp
  16. #include<iostream>
  17. #include<vector>
  18. #include<algorithm>
  19. using namespace std;
  20. using PII=pair<int,int>;
  21. vector<PII> seg;
  22. void merge(vector<PII>& segs){
  23. vector<PII> res;
  24. /*auto cmp=[](const PII a, const PII b){
  25. return a.first>b.first;
  26. //pair排序默认先拍左端点
  27. };*/
  28. sort(segs.begin(),segs.end());
  29. int st=-2e9,ed=-2e9;
  30. for(auto seg:segs){
  31. if(ed<seg.first){
  32. //当前维护的区间不会再有交集了
  33. if(st!=-2e9) res.push_back({st,ed});
  34. st=seg.first,ed=seg.second;
  35. }else{
  36. ed=max(ed,seg.second);
  37. }
  38. }
  39. if(st!=-2e9) res.push_back({st,ed});//防止只有一个区间的情况,and将最后一个区间更新进去
  40. segs=res;
  41. }
  42. int main(){
  43. int n;
  44. cin>>n;
  45. for(int i=0;i<n;i++){
  46. int l,r;
  47. cin>>l>>r;
  48. seg.push_back(make_pair(l,r));
  49. }
  50. merge(seg);
  51. cout<<seg.size()<<endl;
  52. return 0;
  53. }