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

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

#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就行了)

查找左边界的左一个:

int binsearch(int a[],int len,int val)

{

int l=0,r=len-1;

while(l<r){

int m=l+(r-l+1)/2;

if(a[m]<val)

l=m;

else

r=m-1;

}

return l;

}

查找左边界:

int binsearch(int a[],int len,int val)

{

int l=0,r=len-1;

while(l<r){

int m=l+(r-l+1)/2;

if(a[m]<val)

l=m;

else

r=m-1;

}

return l;

}

查找右边界:

int binsearch(int a[],int len,int val)

{

int l=0,r=len-1;

while(l<r){

int m=l+(r-l+1)/2;

if(a[m]<=val)

l=m;

else

r=m-1;

}

return l;

}

查找右边界的右一个:

int binsearch(int a[],int len,int val)

{

int l=0,r=len-1;

while(l<r){

int m=l+(r-l)/2;

if(a[m]<=val)

l=m+1;

else

r=m;

}

return l;

}

例1:Problem - E - Codeforces

476ad941b71541b9af6ccc34f41f0f23.png

e31a23358cd54a8ab63b7e562cc804fe.png

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

法一:用upper_bound()

#include<iostream>

#include<bits/stdc++.h>

using namespace std;

const int N=2e5+5;

int a[N],b[N],c[N];

long long sum[N];

int main()

{

int t,n,q,i,j;

scanf("%d",&t);

while(t--){

scanf("%d%d",&n,&q);

for(i=1;i<=n;i++){

scanf("%d",&a[i]);

sum[i]=sum[i-1]+a[i];

c[i]=max(c[i-1],a[i]);

}

for(i=1;i<=q;i++){

scanf("%d",&b[i]);

}

for(i=1;i<=q;i++){

j=upper_bound(c+1,c+n+1,b[i])-c;

printf("%lld ",sum[j-1]);

}

printf("\n");

memset(sum,0,sizeof(sum));

memset(c,0,sizeof(c));

}

return 0;

}

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

#include<iostream>

#include<bits/stdc++.h>

using namespace std;

int binsearch(int arr[], int len, int val) {

int l = 1, r = len;

while (l < r) {

int m = l + ((r - l) / 2);

if (arr[m]<=val){

l = m + 1;

} else {

r = m;

}

}

return l;

}

const int N=2e5+5;

int a[N],b[N],c[N];

long long sum[N];

int main()

{

int t,n,q,i,j;

scanf("%d",&t);

while(t--){

scanf("%d%d",&n,&q);

for(i=1;i<=n;i++){

scanf("%d",&a[i]);

sum[i]=sum[i-1]+a[i];

c[i]=max(c[i-1],a[i]);

}

for(i=1;i<=q;i++){

scanf("%d",&b[i]);

}

for(i=1;i<=q;i++){

j=binsearch(c,n+1,b[i]);

printf("%lld ",sum[j-1]);

}

printf("\n");

memset(sum,0,sizeof(sum));

memset(c,0,sizeof(c));

}

return 0;

}

例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值满足,否则不满足

#include<iostream>
#include<bits/stdc++.h>
using namespace std;
const int N=2e5+5;
long long a[N],sum[N],c;
int main()
{
    int t,n,d,i,j,l,r,m;
    cin>>t;
    while(t--){
     cin>>n>>c>>d;
     for(i=1;i<=n;i++){
      cin>>a[i];
  }
  sort(a+1,a+1+n,greater<long long>());
  for(i=1;i<=n;i++){
   sum[i]=sum[i-1]+a[i];
  }
  if(a[1]*d<c){
   cout<<"Impossible"<<endl;
  }
  else if(sum[min(n,d)]>=c){
   cout<<"Infinity"<<endl;
  }
  else{
   l=0;
   r=d;
   while(l<r){
    m=l+(r-l+1)/2;
    if(sum[min(m+1,n)]*(d/(m+1))+sum[min(n,d%(m+1))]>=c)
      l=m;
    else
      r=m-1;
   }
   cout<<l<<endl;
  }
  memset(sum,0,sizeof(sum));
 }
 return 0;
}

  • 2
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 3
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值