当前位置:   article > 正文

算法:前缀和 前缀最大值 二分查找

前缀最大值

前言:初学者,查了各种资料,综合起来写了这篇文章

#include<bits/stdc++.h>包含了c++所有头文件,只要包含了这个头文件,可以随便引用所有自带的函数

memset重置清0整数数组(如int a[20];)==>memset(a,0,sizeof(a));

先写上const int N=2e5+5;然后接下来的数组直接写int a[N],b[N];

算法1 前缀和:sum[i]=sum[i-1]+a[i];在算出每个sum[i]之后,要想得到某个区间之和(不管是从1到i还是中间某个区间),可以快速查找

算法2 前缀最大值:c[i]=max(c[i-1],a[i]);求到i为止的最大值(用动态规划的思想,前一位的数已经算出最大值了,那么当前数大的最值就是前一位和本身比),同上,算出之后可以快速查找1到i的最值

算法3 二分查找(需要保证区间中的元素有序): 首先,stl有两个二分查找的函数,可以直接用

对于不降序数组:lower_bound( begin,end,num):从数组的begin位置到end-1位置二分查找第一个大于或等于num的数字,找到返回该数字的地址,不存在则返回end。通过返回的地址减去起始地址begin,得到找到数字在数组中的下标。

upper_bound( begin,end,num):从数组的begin位置到end-1位置二分查找第一个大于num的数字,找到返回该数字的地址,不存在则返回end。通过返回的地址减去起始地址begin,得到找到数字在数组中的下标

另外,写一个二分查找函数(此处以0到n-1为区间)(若以1到n为区间,则将l由0改为1,将r由len-1改为len)(在后面函数调用时,不管是从0开始还是从1开始,第一个参数都是写数组名,第三个参数写要查找的数,第二个参数一般写长度n就行了)

查找左边界的左一个:

  1. int binsearch(int a[],int len,int val)
  2. {
  3. int l=0,r=len-1;
  4. while(l<r){
  5. int m=l+(r-l+1)/2;
  6. if(a[m]<val)
  7. l=m;
  8. else
  9. r=m-1;
  10. }
  11. return l;
  12. }

查找左边界:

  1. int binsearch(int a[],int len,int val)
  2. {
  3. int l=0,r=len-1;
  4. while(l<r){
  5. int m=l+(r-l+1)/2;
  6. if(a[m]<val)
  7. l=m;
  8. else
  9. r=m-1;
  10. }
  11. return l;
  12. }

查找右边界:

  1. int binsearch(int a[],int len,int val)
  2. {
  3. int l=0,r=len-1;
  4. while(l<r){
  5. int m=l+(r-l+1)/2;
  6. if(a[m]<=val)
  7. l=m;
  8. else
  9. r=m-1;
  10. }
  11. return l;
  12. }

查找右边界的右一个:

  1. int binsearch(int a[],int len,int val)
  2. {
  3. int l=0,r=len-1;
  4. while(l<r){
  5. int m=l+(r-l)/2;
  6. if(a[m]<=val)
  7. l=m+1;
  8. else
  9. r=m;
  10. }
  11. return l;
  12. }

例1:Problem - E - Codeforces

476ad941b71541b9af6ccc34f41f0f23.png

e31a23358cd54a8ab63b7e562cc804fe.png

[l,r]是要查找的区间,绝不会查找到区间以外,本题应该查找右边界的右一个并将右端点增加1。如果查找右边界,当要查找的数小于区间所有数时,最后会返回最开始区间最左端,当查找的数大于区间所有数,最后会返回最开始区间最右端,而题目需要的下标是最开始区间最左端的左一个(因为数组开在全局变量,又没有给c[0]赋值,所以c[0]是0,刚好符合需要)以及最开始区间最右端,所以统一不了。而查找右边界的右一个,当要查找的数小于区间所有数时,最后也返回最开始区间最左端,当要查找的数大于区间所有数时,最后也返回区间最右端,但是注意此时最右端是加了1的,因此,同时减1,就能满足题目所需下标,这也对应了upper_bound()函数

法一:用upper_bound()

  1. #include<iostream>
  2. #include<bits/stdc++.h>
  3. using namespace std;
  4. const int N=2e5+5;
  5. int a[N],b[N],c[N];
  6. long long sum[N];
  7. int main()
  8. {
  9. int t,n,q,i,j;
  10. scanf("%d",&t);
  11. while(t--){
  12. scanf("%d%d",&n,&q);
  13. for(i=1;i<=n;i++){
  14. scanf("%d",&a[i]);
  15. sum[i]=sum[i-1]+a[i];
  16. c[i]=max(c[i-1],a[i]);
  17. }
  18. for(i=1;i<=q;i++){
  19. scanf("%d",&b[i]);
  20. }
  21. for(i=1;i<=q;i++){
  22. j=upper_bound(c+1,c+n+1,b[i])-c;
  23. printf("%lld ",sum[j-1]);
  24. }
  25. printf("\n");
  26. memset(sum,0,sizeof(sum));
  27. memset(c,0,sizeof(c));
  28. }
  29. return 0;
  30. }

法二:自己写一个二分查找函数

  1. #include<iostream>
  2. #include<bits/stdc++.h>
  3. using namespace std;
  4. int binsearch(int arr[], int len, int val) {
  5. int l = 1, r = len;
  6. while (l < r) {
  7. int m = l + ((r - l) / 2);
  8. if (arr[m]<=val){
  9. l = m + 1;
  10. } else {
  11. r = m;
  12. }
  13. }
  14. return l;
  15. }
  16. const int N=2e5+5;
  17. int a[N],b[N],c[N];
  18. long long sum[N];
  19. int main()
  20. {
  21. int t,n,q,i,j;
  22. scanf("%d",&t);
  23. while(t--){
  24. scanf("%d%d",&n,&q);
  25. for(i=1;i<=n;i++){
  26. scanf("%d",&a[i]);
  27. sum[i]=sum[i-1]+a[i];
  28. c[i]=max(c[i-1],a[i]);
  29. }
  30. for(i=1;i<=q;i++){
  31. scanf("%d",&b[i]);
  32. }
  33. for(i=1;i<=q;i++){
  34. j=binsearch(c,n+1,b[i]);
  35. printf("%lld ",sum[j-1]);
  36. }
  37. printf("\n");
  38. memset(sum,0,sizeof(sum));
  39. memset(c,0,sizeof(c));
  40. }
  41. return 0;
  42. }

例2:https://codeforces.com/contest/1760/problem/F

e27825a98238427d888b81e560609e3d.png

 6e60c17ca4a0465b9e315cd35790f0ff.png

思路:要找到最大的k(完成了一个任务之后的k天里不能再去做该任务)使得能够在d天里(一天只能完成一个任务)赚到至少c个金币,一共有n个任务,每个任务分配有不同的金币。当k越小时,比如k=0,那么我们可以在这些天每天做金币最高的任务,又比如k=1,我们在第一天做金币最高的任务,第二天做金币第二高的任务,然后第三天又做金币最高的任务,以此类推。首选金币高的任务做,当做不了的时候才选择做金币次高的任务,这样保证我们能获得的金币数尽可能大。k越小,我们可以重复做金币高的任务的次数更多,k越大,我们可以重复做金币高的任务的次数越少,而我们需要找的就是k最大能到多少也能赚到要求的金币(k小于这个最大值时赚到的金币更多)。

因此,我们需要去找到这么一个k值,如果有这么一个k值能使得获得金币数大于等于c,那么k的范围应在[0,d],因为若k大于d,那么在d天里,每天做的任务都不能重复,只能做一次,只能从金币数高的到低的任务一个一个做下来,做的任务数或天数就是n和d中小的那一个,如果有这么一个k值这样做的金币数大于等于c,则k可以无穷大,无论k多大,都是做这几个任务,而且只能做一次,所以在(k,正无穷)没有我们要找的k值。

sort对数组a降序,利于我们找金币个数高的,次高的,等等

利用sum[i]=sum[i-1]+a[i]求前缀和,利于我们找前i天最大金币数的总和

k最小为0,如果当k等于0时,此时每天选择金币最高的那个任务,若d*a[1]<c,则Impossible

当sum[min(n,d)]>=c,则Infinity

而要在[0,d]中找这么一个k值,如果遍历一个一个找,则时间复杂度太高,故选择用二分查找来查找k,利用二分查找,我们要对二分查找模板里的if判断语句进行修改,而且这里不是找边界了,不能完全照搬模板,当该k值满足时,则l=m,去看看是否还有更大的k值(不能写l=m+1,因为可能该k值已经是最大的了,因此要使满足的k仍在[l,r]区间里),当k不满足时,则r=m-1,此时的k太大了,则要去往小了找,且该k值不该在接下来的查找区间里出现了,则r=m-1而不是r=m

那么如何表示该k值是否满足呢:因为题目说完成了一个任务之后的k天里不能再去做该任务,那么实际上在k+1天里出现的每个任务不能重复,只能出现一次,将k+1与n进行比较,如果n小,那么在这k+1天里,只能做这n个任务一次,虽然还有余下的天数,但却什么都做不了;如果k+1小,那么在这k+1天里,将这k+1个任务做完,又从金币最高的任务开始重新做,又开始重新做这k+1个任务。然后用总天数d整除每个循环的任务数,就是要重复做多少个循环,即sum[min(k+1,n)]*(d/(k+1)),然后用总天数对k+1取余,得到剩下还有多少天,首先剩下的天数d%(k+1)肯定小于k+1,因为余数小于除数.若d%(k+1)小于n,则只要做d%(k+1)个任务就行了,若大于n,则也只能做n个任务了,因为在k+1天里任务不能重复,即sum[min(n,d%(k+1))],则

if(sum[min(m+1,n)]*(d/(m+1))+sum[min(n,d%(m+1))]>=c) 那么k值满足,否则不满足

  1. #include<iostream>
  2. #include<bits/stdc++.h>
  3. using namespace std;
  4. const int N=2e5+5;
  5. long long a[N],sum[N],c;
  6. int main()
  7. {
  8. int t,n,d,i,j,l,r,m;
  9. cin>>t;
  10. while(t--){
  11. cin>>n>>c>>d;
  12. for(i=1;i<=n;i++){
  13. cin>>a[i];
  14. }
  15. sort(a+1,a+1+n,greater<long long>());
  16. for(i=1;i<=n;i++){
  17. sum[i]=sum[i-1]+a[i];
  18. }
  19. if(a[1]*d<c){
  20. cout<<"Impossible"<<endl;
  21. }
  22. else if(sum[min(n,d)]>=c){
  23. cout<<"Infinity"<<endl;
  24. }
  25. else{
  26. l=0;
  27. r=d;
  28. while(l<r){
  29. m=l+(r-l+1)/2;
  30. if(sum[min(m+1,n)]*(d/(m+1))+sum[min(n,d%(m+1))]>=c)
  31. l=m;
  32. else
  33. r=m-1;
  34. }
  35. cout<<l<<endl;
  36. }
  37. memset(sum,0,sizeof(sum));
  38. }
  39. return 0;
  40. }

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/2023面试高手/article/detail/434039
推荐阅读
相关标签
  

闽ICP备14008679号