acwing算法基础笔记第一章基础算法

第一章 基础算法

快速排序

在这里插入图片描述
分治是快排的思想,
调整区间后分界点不一定是x
在这里插入图片描述
调整区间方法1
在这里插入图片描述
调整区间方法2
双指针,无论什么时候,i指针前面的数都是小于x的,同理j指针后面的数都是大于x的,所以当两个指针相遇穿过,就分成了两个区间,两个指针穿过了就不能交换指针了,
每次交换完两个指针后,都会往中间移动一格,因此可以先不管,直接把两个指针往中间移动一次,然后再进行判定,因此两个指针就需要把两个指针放到边界的左右两侧一格,这样最开始移动一格后,才能到真正的边界,
在这里插入图片描述

#include <iostream>
using namespace std;

const int N = 1e6 + 10;
int n;
int q[N];

void quick_sort(int q[], int l, int r)
{
    if (l >= r) return;//判边界,如果里面只有一个数或者没有数就return
    int x= q[l],i= l- 1, j=r +1;//分界点x,如果下面quick_sort里写i,此处不能取q[l]因为会发生死循环,无限递归,如果下面下面quick_sort里写j和j+1,此处不能取q[r],会发生无限递归
    while (i < j)
    {
        do i ++ ; while (q[i] < x);
        do j-- ; while (q[j] > x);
        if(i< j) swap(q[i],q[j]);
        //{
        //int t = q[i];
        //q[i] = q[j];
        //q[j] = t;
        //}如果没有swap函数自己写
    }
    quick_sort(q,l,j);//quick_sort(q,l,i-1);注意此处写i了则上面的分界点x就不能取l,因为会发生死循环,如果只有两个数,那么可能会使某个递归一直循环于[0,1]的区间的两个数,同理此处写j和j+1的时候,边界点x就不能取到q[r],否则会发生死循环
    quick_sort(q,j+ 1,r);//quick_sort(q,i,r);
}


int main()
{
  scanf("%d",&n);
  for (int i = 0; i < n; i ++ ) scanf("%d", &q[i]);
    quick_sort(q,0,n-1);
    for (int i = 0; i< n; i ++ ) printf("%d ", q[i]);
    return 0;  
}

归并排序

归并排序的思想也是分治,但是分治的思想和快排不一样,快排是用一个数来分,分完后左边都比这个数小,右边都比这个数大,归并是以整个数组的中心中间来分,并且是先递归排序左边和右边两边,两边排好序后再合二为一,把两个有序序列合成一个有序序列,在合并的过程中,第一个指针最多只能扫描左边的半边,第二个指针最多只能扫描右边的半边,因此两个指针扫描的总共的长度是O(n),所以合并的时间复杂度是O(n),即每个元素只会被移动一次,快排是先分完再递归两边,
在这里插入图片描述
归并排序是稳定的,所以两个相同的数排的时候,要把在前面的那个数先放到序列里,快排是不稳定的,可以把快排里都弄成pair二元组,这样相同的数的下标肯定不同,双关键字排序就肯定能保证所有数都是不同的,这样就可以稳定排序了,
快排的平均时间复杂度是O(nlog2n),最坏时间复杂度是O(n²)但是几乎不可能达到,归并排序的时间复杂度一定是O(nlog2n)
归并排序第一层是n的长度,第二层是n/2的长度,第三层是4个n/4的长度,到最后是n个长度为1的区间,n除以log2n就会变成1,所以总共有logn层,每一层是时间复杂度是O(n)的,第一层是O(n),第二层是两个O(n/2)加一起就是O(n),第三层是4个O(n/4)加一起就是O(n),所以总共就是O(nlogn)
在这里插入图片描述
快排也类似,虽然每次划分区间的时候不一定是恰好n/2,但是期望是n/2,所以整个递归的层数也是期望是logn,每层是O(n),一共logn层,所以是O(nlogn)
在这里插入图片描述

在这里插入图片描述
归并排序需要一个tmp数组来存储最终把两个序列合并成一个序列后的结果,k表示已经合并了几个数了,

#include <iostream>
using namespace std;

const int N = 100001;

int n;
int q[N],tmp[N];

void merge_sort(int q[], int l, int r)//q是要排序的数组,l和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;
    //归并的过程,i是指向左半边区间的起点,j是指向右半边区间的起点,
    while (i <= mid && j <= r)
        if (q[i] <= q[j]) tmp[k ++ ] = q[i ++ ];
        else tmp[k ++ ] = q[j ++ ];
    while (i <= mid) tmp[k ++ ] = q[i ++ ];
    while (j <= r) tmp[k ++ ] = q[j ++ ];
    
    for (i = l,j= 0; i <= r; i ++, j ++ ) 
    q[i] = tmp[j];
    
    
}

int main()
{
    scanf("%d",&n);
    for (int i = 0;i < n;i++ ) scanf("%d", &q[i]);
    
    merge_sort(q,0,n - 1);
    
    for (int i = 0; i< n; i ++ ) printf("%d ", q[i]);
    return 0;  
}

整数二分

如果有单调性,就一定可以二分,但是可以二分的题目不一定非要有单调性,即没有单调性也有可能可以二分,二分的本质并不是单调性,二分的本质是区间,如果可以找到这样一个性质,可以把整个区间一分为二,比如右半边满足,左半边不满足(没有交点),二分就可以寻找性质的边界,就可以把边界点二分出来,既可以寻找满足的边界,也可也寻找不满足的边界(下图红绿部分上面的箭头),二分出来绿色的点和红色的点就是两个不同的模板,

//模板
// 区间[1,r]被划分成[l,mid]和[mid + 1,r]时使用:mid属于左边用此模板,此时mid=l+r>>1不用+1
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]被划分成[1,mid - 1]和[mid,r]时使用:mid属于右边用此模板,此时mid=l+r+1>>1,要有加一

int bsearch_2(int 1, int r)
{
   while (l<r) 
   {
       int mid =l+r+1>> 1;
       if (check(mid)) l = mid;
       else r = mid - 1;

   }
   return l;
}

二分第一步先找中间值mid,每次判断中间值mid是否满足此性质,假设是红色的性质,即check一下是否满足红色的性质,如果check是true,说明mid满足红色的条件,此时mid就在左边红色区间里面,那么答案就应该在【mid,r】,包含mid,此时点是可以取到红色的边界点,因为mid可能是答案,更新方式就是把l,r这个区间更新成【mid,r】区间,所以更新方式就是l=mid,因为r是不变的,如果当mid不满足红色性质的时候,那么mid就一定取在绿色的部分,那么此时答案就在【l,mid-1】里面,因为mid是不满足红色性质的,所以答案边界一定不在mid上,取不到mid,此时更新方式就是把l,r更新成【l,mid-1】区间,l是不变的,把r更新成mid-1就可以,
当想二分绿色的时候,首先求mid=l+r>>1,然后check一下mid是否满足绿色的性质,如果是true说明mid是满足绿色的性质的,那么mid就在绿色这段里,答案就应该在l和mid之间,即答案边界点一定在mid 的左边,并且边界点有可能在mid上,更新方式就是把l,r更新成【l,mid】,反之如果mid不满足绿色的性质,那么mid就会在红色这半边,此时答案就应该在【mid+1,r】之间,不能取到mid,更新方式就是把l,r更新成【mid+1,r】
在这里插入图片描述
每次看一下区间是l=mid还是r=mid,l=mid就要补上加一,r=mid就不需要补上加一,因为如果l=r-1,l和r只相差1,如果不补上加一的话,那么mid=l+r>>1向下取整应该等于l,如果此时check是成功的返回true,那么在更新区间的时候,l=mid=l,因此此时更新后l和r还是l和r,没有变化,循环了一遍后l和r没有变,那么下次循环后也不会变,所以就会死循环,因此要补上加一,此时mid =l+r+1>>1就是r,此时更新后l=mid=r,即【l,r】循环一遍后变成【r,r】,那么就会停止,不会发生死循环
c++里的除法是下取整,
具体不用考虑是红色的边界点,还是绿色的边界点,写的时候直接先求mid,然后check判断一下根据check的值想一下答案应该怎么划分,应该是l=mid还是r=mid,如果是第一种,在求mid 的时候补上1就可以了,第二种就不需要补上1,
在这里插入图片描述

#include <iostream>
using namespace std;
const int N = 100010;
int n, m;
int q[N];
//二分的主要思想:在一个区间内部,每次选择答案所在的区间进行下一步的处理,每次把区间的长度缩小一半,选择答案所在的一个区间,每一次都能保证区间里面一定有答案,当区间长度是1的时候,这个区间里面的这个数一定就是答案,二分是一定有解的,比如要找到等于x的这个数,但是数组里面可能不包含x,所以定义性质的时候就要找到从左往右看,第一个满足≥x的数,性质是一定有边界的,
int main()
{
    scanf("%d%d",&n, &m);
    for (int i = 0; i< n; i++ ) scanf("%d", &q[i]);
    
    while (m -- )
    {
        int x;
        scanf("%d",&x);
        
        int l=0,r=n- 1;
        while (l<r)
        {
         int mid = l +r >> 1;//起始位置不管直接先写mid=l+r,后面再看要不要补加一,然后check条件判断x的起始位置应该是≥x,所以如果满足,那么mid一定在右半边,那么答案即边界的位置一定在左半边,mid也满足这个≥x的性质,mid也有可能取到答案,因此要更新成l到mid,反之一定是l=mid+1。此时是r=mid,所以是第一个模板,就不用加一了,
         if (q[mid] >= x) r = mid;
         else l = mid + 1;   
        }
        if (q[l] != x) cout <<"-1 -1" << endl;//数组里不存在这个值
        else
        {
         cout << l <<' '; 
         int l = 0, r = n - 1;//右边界,假设最后一个x,可以定义成小于等于x,≤x,此时左边数包括x都满足性质,如果满足≤x的性质,说明mid在左半边,所以答案是在mid 的右半边,并且mid也满足这个性质,mid也有可能是答案,所以更新区间就要把mid 放到区间里,也就是从mid到r,反之r=mid-1,此时因为是l=mid,所以要补上加一,
         while (l <r)
         {
           int mid =l+r+1>> 1;
           if (q[mid] <= x) l = mid;
           else r = mid - 1;  
         }
         cout << l << endl;
        }
    }
        return 0;
}

浮点数二分

因为浮点数边界没有整除,每次区间长度可以严格的缩小一半,所以不需要处理边界,每次都保证答案在区间内部,每次通过中间点判断一下答案落在哪半边,只要每次保证答案落在区间里就可以了,当区间长度很小的时候就可以认为找到了答案,比如r-l≤10的-6次方了,此时就可以用l或r当作自己的答案
求根号x的值,

#include <iostream>
using namespace std;
int main()
{
    double x;
    cin >> x;
    
    double l = 0,r = x;
    while (r - l > 1e-8)//如果题目说保留四位小数,就写e-6,如果保留5位小数,就写e-7,保留6位小数,就写e-8,至少要比要求的有效位数多2,
    //还可以不用精度迭代,可以直接循环100次,for (int i = 0;i< 100; i ++ ),相当于把整个区间的长度除以2的100次方,
    {
        double mid = (l + r) / 2;
        if (mid * mid >= x) r = mid;
        else l = mid;
    }
    printf("%lf\n",l);//输出r或者l都可以
    return 0;
}

高精度

java有大整数类,没有位数要求,python数自带的默认数的范围是无限大,c++里没有大整数类,当两个很大的整数进行加减乘除的时候,需要高精度,
四种:①两个比较大的整数相加,两个数的位数是10的六次方,②两个比较大的整数相减,③一个大整数乘上一个比较小的整数,大整数位数小于10的六次方,小的数的数值小于10的九次方或者小于10000,⑤一个比较大的数除以一个比较小的数,求商和余数,
大整数用int存不下来,其实是把每一位存到数组里,存的时候数组下标为0的地方存数的个位,下标为1的存十位,
加减乘除里面,大整数的存储都是一致的,

高精度加法

在这里插入图片描述

#include <iostream>
#include <vector>

using namespace std;

const int N = 1e6 + 10;

// C=A+B
vector<int> add(vector<int> &A,vector<int> &B)//加上引用就不会拷贝整个数组了,效率提高
{
    vector<int> C;
    
    int t =0;//最开始进位是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);//A和B的位加上进位组成C的每一位
        t /= 10;
    }
    if (t) C.push_back(1);//如果最高位有进位,则加上一个1,
    return C;
}

int main()
{
    string a,b;
    vector<int> A,B;
    cin >>a>>b;// a="123456"
    for (int i = a.size() - 1; i >= 0; i --)A.push_back(a[i]-'0');// A = [6,5,4,3,2,1]
    for (int i = b.size() - 1; i >= 0;i-- ) B.push_back(b[i]-'0');
    auto C = add(A,B);
    for (int i = C.size() - 1; i >= 0; i-- ) printf("%d",C[i]);
    return 0;
}

高精度减法

大整数减法:第一步判断如果A>B,直接算A-B, 如果A<B,就计算-(B-A),每次运算都是较大的数减去较小的数,一定不会出现负数的情况,因此最高位一定不会再往前借位了,可以少处理一些边界情况,
在这里插入图片描述

#include <iostream>
#include <vector>
using namespace std;

// 判断是否有 A>= B
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;//如果两个数每一位都相等,返回true,因为等于也属于A>=B      
}
//如果t≥0,就是t本身,如果t<0,就是t+10 ,所以用(t+10)%10把这两种情况合二为一
//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];//判断一下B的位数是否存在,因为B的位数可能比A的少,
        C.push_back((t + 10) % 10);
        if (t<0) t=1;//t=0表示没有借位,t=1表示借位了,
        else t = 0;
    }
    while (C.size() > 1 && C.back()== 0) C.pop_back();//去掉前导0
    return C;
}


int main()
{
    string a, b;
    vector<int> A,B;
    cin >>a>>b;//a="123456" 
    for (int i = a.size() - 1; i >= 0; i --)A.push_back(a[i]-'0');// A = [6,5,4,3,2,1]
    for (int i = b.size() - 1; i >= 0;i-- ) B.push_back(b[i]-'0');
    
    if (cmp(A,B))
    {
        auto C = sub(A,B);
        for (int i= C.size() - 1; i >= 0; i -- ) printf("%d",C[i]);
        
    }
    else
    {
        auto C = sub(B,A);
        
        printf("-");
        for (int i = C.size() - 1; i >= 0; i -- ) printf("%d",C[i]);
    }
    return 0;
}

高精度乘法

在这里插入图片描述
在这里插入图片描述

#include <iostream>
#include <vector>
using namespace std;



vector<int> mul(vector<int> &A,int b)
{
    vector<int> C;
    int t = 0;
    for (int i = 0; i<A.size() || t; i++ )//要不是i没有循环完,要不是t不为0
    {
      if (i< A.size()) t += A[i]* b;
      C.push_back(t % 10);
      t /= 10;
    }
    return C;  
}


int main()
{
    string a;
    
    int b;
    cin >> a >> b;
    
    vector<int> A;
    for (int i = a.size() - 1; i >= 0; i-- ) A.push_back(a[i] -'0');
    auto C = mul(A, b);
    for (int i= C.size() - 1; i >= 0; i -- ) printf("%d", C[i]);
    return 0;
}

高精度的整数除以低精度的整数,

高精度除法

#include <iostream>
#include <vector>
#include<algorithm>
using namespace std;


//A/b 商是C,余数是r,
//加法减法乘法都是从第一位开始算,而除法是从最高位开始算,
vector<int> div(vector<int> &A,int b,int &r)//余数是通过引用传回去的
{
    vector<int> C;
    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,去掉前导0
    return C;  
}


int main()
{
    string a;
    
    int b;
    cin >> a >> b;
    
    vector<int> A;
    for (int i = a.size() - 1; i >= 0; i-- ) A.push_back(a[i] -'0');
    int r;
    auto C = div(A, b,r);
    for (int i= C.size() - 1; i >= 0; i -- ) printf("%d", C[i]);
    cout <<endl;
    cout <<r<<endl;
    return 0;
}

前缀和

在这里插入图片描述
在这里插入图片描述
下标从1开始,S0=0,更好处理边界问题,可以用相减的方式统一的处理,
在这里插入图片描述

在这里插入图片描述
前r个数的和减去前l-1个数的和就是l到r个数的和,
ios::sync_with_stdio(false); //让cin和标准输入输出不同步,作用是提高cin的读取速度,副作用是不能再使用scanf了,

#include <iostream>
using namespace std;

const int N = 10010;

int n, m;
int a[N], s[N];
int main()
{
    scanf("%d%d",&n,&m);
    for (int i = 1; i <= n; i ++ ) scanf("%d", &a[i]);
    
    for (int i = 1; i <= n; i ++ ) s[i] = s[i - 1] + a[i];//前缀和的初始化
    while (m -- )
    {
       int l, r;
       scanf("%d%d",&l,&r);
       printf("%d\n",s[r] - s[l - 1]);//区间和的计算

    }
    return 0; 
}

二维前缀和

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

#include <iostream>
const int N = 1010;

int n, m, q;
int a[N][N],s[N][N];
int main()
{
    scanf("%d%d%d",&n, &m, &q);
    for (int i= 1;i <= n;i ++ )
        for (int j= 1;j <= m; j ++ )
            scanf("%d",&a[i][j]);
    for (int i = 1; i <= n; i ++ )
        for (int j= 1; j <= m; j ++ )
            s[i][j] = s[i - 1][j] + s[i][j - 1] - s[i - 1][j - 1] + a[i][j];//求前缀和
    while (q -- )
    {
        int x1, y1, x2, y2;
        scanf("%d%d%d%d",&x1,&y1,&x2,&y2);
        printf("%d\n",s[x2][y2] - s[x1 - 1][y2] - s[x2][y1 - 1] + s[x1 - 1][y1 - 1]);//算子矩阵部分和
    }

return 0;

}

差分是前缀和的逆运算

a数组是b数组的前缀和,那么b数组就称为a数组的差分,
想让a数组里某个区间里的数加上c的话,只需修改b数组里的两个数就可以,可以用O(1)复杂度给原数组某一个区间的数全部加上一个固定的值,bl加上c使得a数组al以及al之后的所有的a数组的元素都会加上c,而因为r之后的a不需要加上c,所以br+1减去c使得a数组的r+1之后的元素都减去一个c,注意此处a是b的前缀和
在这里插入图片描述

在这里插入图片描述
在这里插入图片描述

#include <iostream>
using namespace std;

const int N = 100010;

int n, m;
int a[N],b[N];
void insert(int l, int r, int c)
{
   b[l] += c;
   b[r+1]-= c; 
}

int main()
{
    scanf("%d%d",&n,&m);
    for (int i = 1; i <= n; i ++ ) scanf("%d", &a[i]);
    for (int i = 1; i <= n; i ++ ) insert(i,i, a[i]);//a数组的每个值都是插入进去的,这样与计算后面的b【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];
    for (int i = 1; i <= n; i ++ ) printf("%d ",b[i]);
    
    return 0;
}

二维差分

二维差分是给其中的子矩阵加上一个固定的值,
ai,j是差分矩阵bi,j的前缀和,差分不用考虑构造,假定刚开始ai,j都是0,将a数组每个元素插入就可以了
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

#include <iostream>
using namespace std;

const int N = 1010;
int n, m, q;
int a[N][N],b[N][N];

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;
}

int main()
{
    scanf("%d%d%d",&n,&m, &q);
    
    for (int i = 1; i <= n; i ++ )
        for (int j= 1; j <= m; j ++ )
         scanf("%d",&a[i][j]);
         
    for (int i = 1;i<= n; i ++ )
        for (int j= 1; j<= m; j ++ )
            insert(i,j,i,j,a[i][j]);
    while (q -- )
    {
        int x1,y1, x2, y2,c;
        cin >> 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 = 1; i <= n; i ++ )
    {
        for (int j= 1; j<= m; j ++ )printf("%d ",b[i][j]);
        puts("");
    }
        
return 0;
}

双指针算法

第一类:在两个序列里面,一个指针指向其中一个序列,另外一个指针指向另外一个序列,
第二类:指向一个序列,
双指针就可以把暴力做法O(n²)优化到O(n)
在这里插入图片描述
每个单词输出占一行
在这里插入图片描述

#include <iostream>
#include <string.h>
using namespace std;

int main()
{
   char str[1000];
    fgets(str,100,stdin);
    
    int n = strlen(str);
    for (int i = 0; i< n; i ++ )
    {
        int j = i;
        while (j< n && str[j] !=' ') j++ ;
        // 这道题的具体逻辑
        for (int k = i; k < j; k ++ ) cout << str[k];
        cout << endl;
        i=j;
    }
    return 0; 
}

在这里插入图片描述
红色是i,绿色是j,j是单调往后走的,
在这里插入图片描述
a是原来的数组,s数组存的是当前j到i区间内每一个数出现的次数,

#include <iostream>
using namespace std;
const int N= 100010;

int n;
int a[N],s[N];
int main()
{
    cin >> n;
    for (int i= 0;i<n;i++ ) cin >> a[i];
    
    int res = 0;
    for (int i= 0,j=0;i<n; i++ )
    {
        s[a[i]] ++ ;
        while (s[a[i]] > 1)
        {
             s[a[j]]--;
             j++ ;
        }
        res = max(res,i - j + 1);
    }
    cout << res << endl;
    return 0;
}

位运算

在这里插入图片描述

#include <iostream>#include <string.h>
using namespace std;
int main()
{
    int n = 10;
    for (int k = 3; k >= 0; k -- ) cout << (n >> k & 1) ;
    return 0;
}


在这里插入图片描述
lowbit可以统计一下x里面1的个数,
在这里插入图片描述

#include <iostream>
using namespace std;

int lowbit(int x)
{
    return x & -x;
}

int main()
{
    int n;
    cin >> n;
    while (n -- )
    {
        int x;
        cin >> x;
        int res = 0;
        while (x)x -= lowbit(x),res ++ ;// 每次减去x的最后一位1
        cout << res << ' ';
    }
    return 0;
}

整数的保序的离散化

保序:小的一定在前面,大的一定在后面,
unique是把数组中所有的元素进行去重,并且返回去重之后的数组的尾端点,剩余重复的元素会放到最后的部分用erase删掉就可以了,
在这里插入图片描述
在这里插入图片描述

//离散化
vector<int> alls;// 存储所有待离散化的值
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
}


在这里插入图片描述
在这里插入图片描述
映射成的值就是数组的下标,
在这里插入图片描述
把里面用到过的不同的数映射成从1开始的自然数,
在这里插入图片描述
读入的区间的两个端点也需要离散化,最开始插入的c值的下标也需要离散化,find把x映射成从1开始的自然数,

#include <iostream>
#include <vector>
#include <algorithm>
using namespace std;

typedef pair<int,int> PII;

const int N = 300010;

int n, m;
int a[N],s[N];
vector<int> alls;
vector<PII> add,query;
//二分求出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
}
int main()
{
    cin >> n >> m;
    for (int i= 0;i<n;i ++ )
    {
        int x, c;
        cin >>x >>c;
        add.push_back({x,c});
        alls.push_back(x);
    }
    for (int i=0;i<m; i ++ )
    {
        int l, r;
        cin >> l >>r;
        query.push_back({l,r});
        
        alls.push_back(l);
        alls.push_back(r);
    }
    //去重
    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(); 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;
}

在这里插入图片描述

//unique函数
vector<int>::iterator unique(vector<int> &a)
{
    int j = 0;
    for (int i= 0;i<a.size(); i ++)
        if(!i || a[i] != a[i - 1])
            a[j++]= a[i];
            // a[0]~a[j- 1] 所有a中不重复的数
    return a.begin() + j;
}

区间合并

在这里插入图片描述
在这里插入图片描述
把有交集的区间合并成一个区间,
在这里插入图片描述
在这里插入图片描述
pair在c++里排序会优先以左端点排序,再以右端点排序,

#include <iostream>
#include <algorithm>
#include <vector>

using namespace std;

typedef pair<int, int> PII;

const int N = 10001;

int n;
vector<PII> segs;

void merge(vector<PII> &segs)
{
    vector<PII> res;
    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});//把左边的区间放进res,不是把segs里的当前的seg放入res,表示没有交集
            st = seg.first,ed = seg.second;//更新st 和seg
        }
        else ed = max(ed, seg.second);//否则区间有交集
        if (st != -2e9) res.push_back({st,ed});
        segs = res;
}
int main()
{
    cin >> n;
    for (int i = 0;i<n;i ++ )
    {
      int l, r;
      cin >> l >> r;
      segs.push_back({l,r});
 
    }
    merge(segs); 
    cout << segs.size() << endl;
    return 0;
}

习题

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

#include <iostream>
using namespace std;

const int N = 100010;

int n, k;int q[N];

int quick_sort(int l, int r, int k)
{
    if (l == r) return q[l];//此处可以写≥,因为和二分一样,时刻保证区间里最少有一个数,但是快排必须写l≥r,因为快排里面,区间里面可能是没有数的,有可能出现l>r的情况,
    int x = q[l],i= l- 1, j=r + 1;
    while (i < j)
    {
        while (q[ ++ i]< x);
        while (q[-- j] >x);
        if (i< j) swap(q[i],q[j]);
    }
    int sl=j-l+1;//左半边区间是l到j,右半边区间是j+1到r,左半边从l到j一共j-r+1个数 
    if (k <= sl) return quick_sort(l, j, k);
    return quick_sort(j + 1, r, k - sl);
}
int main()
{
    cin >> n >> k;
    for (int i = 0; i< n; i ++ ) cin >> q[i];
    cout << quick_sort(0, n - 1, k) << endl;
    return 0;
}

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

#include <iostream>

using namespace std;

typedef long long LL;

const int N = 100010;

int n;int q[N], tmp[N];

LL merge_sort(int l, int r)
{
    if (l >= r) return 0;
    int mid = l + r >> 1;
    LL res = merge_sort(l, mid) + merge_sort(mid + 1, r);
    // 归并的过程
    int k = 0,i = l, j = mid + 1;
    while (i <= mid && j <= r)
        if (q[i] <= q[j]) tmp[k ++ ] = q[i ++ ];
        else
        {
            tmp[k ++ ] = q[j ++ ];
            res += mid - i + 1;//i后面的数都比j大,都能构成逆序
        }
    // 扫尾的时候一定有一边已经扫完了,此时没有数对了,所以不需要加了,下面两个循环最多只会执行一个循环,
    while (i <= mid) tmp[k ++ ] = q[i ++ ];
    while (j <= r) tmp[k ++ ] = q[j ++ ];
    // 物归原主
    for (int i= l,j= 0; i <= r;  i++, j ++ ) q[i] = tmp[j];
    return res;
}
int main()
{
    cin >> n;
    for (int i = 0; i < n; i ++ ) cin >> q[i];
    cout << merge_sort(0, n - 1) << endl;
    return 0;
}

在这里插入图片描述

在这里插入图片描述
浮点数的二分
printf是默认保留6位小数的,

#include <iostream>
using namespace std;

int main()
{
    double x;
    cin >> x;
    double l = -10000,r = 10000;
    while (r - l> 1e-8)
    {
        double mid = (l + r) / 2;
        if (mid * mid *mid >= x) r = mid;
        else l = mid;

    }
    printf("%lf\n",l);
    return 0;
}

在这里插入图片描述

在这里插入图片描述
在这里插入图片描述

#include <iostream>
using namespace std;

const int N = 100010;

int n, m;
int a[N],s[N];

int main()
{
    cin >> n >> m;
    for (int i= 1;i <= n; i ++ ) cin >> a[i];
// 求前缀和数组
    for (int i = 1;i <= n; i++ ) s[i] = s[i - 1] + a[i];
    while (m -- )
    {
        int l, r;
        cin >> l>> r;
        cout << s[r] - s[l - 1] << endl;
    }

return 0;
}

在这里插入图片描述
S[i,j]的含义就是每一个【i,j】这个格子左上角所有数的和
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
有减一的操作最好从下标从1开始,

#include <iostream>

using namespace std;

const int N = 1010;

int n, m, q;
int a[N][N],s[N][N];
int main()
{
    scanf("%d%d%d",&n,&m,&q);
    for (int i=1;i <= n;i ++ )
        for (int j= 1;j<= m; j ++)
            scanf("%d",&a[i][j]);
// 初始化前缀和数组
    for (int i = 1; i <= n; i ++ )
        for (int j = 1; j <= m; j ++ )
            s[i][j] = s[i - 1][j] + s[i][j - 1] - s[i - 1][j - 1] + a[i][j];
// 询问
    while (q -- )
    {
        int x1, y1,x2, y2;
        scanf("%d%d%d%d",&x1,&y1,&x2,&y2);
        printf("%d\n",s[x2][y2] - s[x2][y1 - 1] - s[x1 - 1][y2] + s[x1 - 1][y1 - 1]);
    }
    
return 0;
}
    

差分:前缀和的逆运算
在这里插入图片描述
在这里插入图片描述
本来需要循环一遍,把O(n)时间复杂度变成O(1)
初始化的时候假定a数组都是0,此时构造的差分数组也全都是0,可以看成在全是0的数组上进行了n步的插入操作,
在这里插入图片描述
在这里插入图片描述

#include <iostream>
using namespace std;
const int N = 100010;
int n, m;
int a[N], b[N];
void insert(int l, int r, int c)
{
   b[l] += c;
   b[r +1] -= c; 
}

int main()
{
    cin >> n >> m;
    for (int i = 1; i <= n; i ++ ) cin >> a[i];
    for (int i = 1; i <= n; i ++ ) insert(i, i, a[i]);
    while (m -- )
    {
        int l, r, c;
        cin >> l >> r >> c;
        insert(l,r,c);
    }

    for (int i= 1; i <= n; i ++ )a[i]= a[i - 1] + b[i];
    for (int i = 1; i <= n; i ++ ) printf("%d ",a[i]);
    puts("");
return 0;
}

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

#include <iostream>

using namespace std;

const int N = 1010;

int n, m, q;
int a[N][N],b[N][N];
void insert(int x1, int y1, int x2, int y2, int c)
{
    b[x1][y1] += c;
    b[x1][y2 + 1]-=c;
    b[x2 + 1][y1]-=c;
    b[x2 + 1][y2 + 1] +=c;
}

int main()
{
    scanf("%d%d%d",&n, &m, &q);
    for (int i = 1; i <= n; i ++ )
        for (int j = 1; j <= m; j ++ )
            scanf("%d",&a[i][j]);
    for (int i = 1; i <= n; i ++ )
        for (int j= 1;j <= m; j++ )
            insert(i,j,i,j,a[i][j]);
    while (q--)
    {
        int x1, y1, x2, 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++ )
            a[i][j] = a[i - 1][j] + a[i][j - 1] - a[i - 1][j - 1] + b[i][j];
            
    for (int i = 1; i <= n; i ++ )
    {
        for (int j= 1; j <= m; j ++ ) printf("%d ",a[i][j]);
        puts("");
    }

return 0;
}

  • 13
    点赞
  • 11
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
### 回答1: acwing算法基础课是一门针对算法学习的在线课程,在这门课程中,学生可以系统地学习和掌握算法基础知识,提高编程水平。为了方便学生学习,acwing提供了网盘服务。 acwing算法基础课网盘是一个用于存储课程资源的平台。通过这个网盘,学生可以下载课程讲义、代码模板以及补充材料等。这些资源都经过精心整理,供学生们参考和学习。 网盘中的资源是按照课程章节进行分类的,学生可以根据自己的学习需要,选择性地下载所需的资料。同时,网盘还提供了搜索功能,方便学生快速定位和获取所需资料。 acwing算法基础课网盘的使用对于学生们的学习非常有帮助。通过下载和学习这些资源,学生们可以更好地理解课程内容,加深对算法的理解。此外,学生们还可以通过研究代码模板,学习优秀的编程思想和技巧,提高自己的编程能力。 总之,acwing算法基础课网盘是一项非常便利和实用的服务,为学生们提供了更加全面和深入的学习资源,帮助他们更好地掌握和运用算法知识。 ### 回答2: acwing算法基础课是一门优质的算法学习资源,其中的课程内容丰富多样,涵盖了算法基础知识、数据结构、动态规划、图论等等。很多学习者都认为这门课程对他们的算法学习有很大的帮助。 网盘是指以网络为媒介,提供文件存储和下载服务的云存储平台。acwing算法基础课也提供了网盘服务,方便学习者下载课程资料并进行学习。 通过acwing算法基础课网盘,学习者可以方便地获取到课程的各种学习资料,包括讲义、习题集、代码示例等。这些资料可以帮助学习者更好地理解和掌握课程的内容。此外,网盘还提供了上传和分享功能,学习者可以将自己的学习心得、代码等资料分享给其他学习者,促进学习者之间的互相学习和交流。 acwing算法基础课网盘的优点不仅仅是方便快捷的下载和分享功能,还包括安全可靠的存储环境。学习者可以放心地将自己的学习资料上传到网盘进行备份,减少数据丢失的风险。同时,网盘还提供了多种存储空间容量的选择,满足学习者不同的需求。 总的来说,acwing算法基础课网盘为学习者提供了方便、安全和多样化的学习资源下载和分享服务,为学习者的算法学习和进步提供了有力的支持。如果你对算法感兴趣,我推荐你去尝试一下这门精彩的课程!

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值