首先,我是从一本通那儿过来的,但因为数据范围不同,所以正解代码就不同,那里 n 极其大,但这里确是 极其大,所以我们必须二分答案。代码不难,但是细节比较多。作为一名 CQ OIer,感触较深。
正确性证明
首先,我们因为要求最优解,所以说我们可以来证明一下,如果已经求出的解为 $ans$,那么答案一定大于等于它,因为这个解已经成立了,不可能取比它小的了,所以满足了单调性。
二分方法
首先,左端点为 0,表示一个都不能成套,最大区间为 $7.5\times 10^8$。最坏数据如下:
2 500000000
500000000 500000000 500000000
此时答案为 ,如果不会算,设成 也无妨。
二分的 check 函数可以这样来分析:
首先,设当前判断的是 x,那么我们就可以这样想:如果它的牌本来就够,那么就用它自己的,把 joker 给其他牌,这样一定能满足,如果给了它,冗余的也没用,所以我们加上的就是 ,但是我们不能让它变为负数,所以说还要与就求不够的牌数,但是它肯定是自然数,所以还要和 0 取最大值,这就是它缺少的。一共进行 x 轮,每次最多补一个,在保证足够的情况下,一共只能补 次,所以记录 t 为缺少牌的数量,然后如果 ,那么可以取 x 次,返回 1,否则返回 0。
有了详细的二分,我们只需要套个二分模板,但要注意的是,区间终点必须偏右,否则只有 40 分,原因如下:
首先,但判断到最后一次时,l=r-1,此时 ,如果成立,那么 l=mid 一直执行下去,即一直满足 l<r,等死恒成立,二分不会退出,就会变成死循环,所以说我们在最后一次时区间终点必须偏右。
AC Code:
#include<bits/stdc++.h>
#define int long long //t有可能爆 int
using namespace std;
int n,a[55],m,l,r=7.5e8;//l 左端点,r 右端点
bool check(int x){
int t=0;
for(int i=1;i<=n;i++){
t+=max(x-a[i],0ll);//累加缺少的牌数
if(t>min(x,m))
return 0;//如果不够就不行
}
return 1;//判断到最后都可以就是可行的
}
signed main(){
scanf("%lld %lld",&n,&m);
for(int i=1;i<=n;i++)
scanf("%lld",&a[i]);
while(l<r){
int mid=l+r>>1;
if(l==r-1)
mid++;//向上取整,否则40分
if(check(mid))
l=mid;
else
r=mid-1;//左边可以取,右边不能取
}//二分模板
printf("%lld\n",r);//取当前的右端点
return 0;
}