赞
踩
前言:初学者,查了各种资料,综合起来写了这篇文章
#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;
-
- }
[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
思路:要找到最大的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;
- }
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。