由于我是直接写的hard版本的,而hard版本和easy版本只有数据范围的差距,所以在此只阐述hard版本的题解(easy版也能过,但是easy的数据范围应该还有其他方法可以偷跑过去)
假设x天可以完成,那么x+1天肯定可以,因为x+1天的第一杯咖啡是没有损耗的(本题中由于每多一天,咖啡效果-1,所以记作孙损耗)
假设x天无法完成,那么x-1天肯定不行,因为x天起码第一杯咖啡是没有损耗的
故此,可看出本题中的天数具有单调性。
那二分得到单调性之后怎么处理呢?
先把n杯咖啡从小到大排序
假设我当前二分的天数是x天,那么我先把前x, 即[n - x + 1, n]最大的咖啡拿过来,然后我往前再拿x杯咖啡
直到当前的[l, r]中有某一个咖啡的大小是大于当前的损耗的,我再次二分找到这一个咖啡
假设是第t杯,我只用加上[t + 1, r] - 损耗,然后我就不用再往前找咖啡了,因为必定是大于之后的损耗的
那么我把现在的咖啡的效果累加减去损耗后要是大于m,即二分可行,否则二分不行
这里,对于[l, r]的咖啡的值由于只涉及查询不涉及更新,所以我用了前缀和维护
二分的效率是logN * logN,应该还没有N大,所以可以看做是O(N)(应该吧)
#include<bits/stdc++.h>
#define LL long long
using namespace std;
const int MAXN = 2e5 + 10;
LL cs[MAXN], lei[MAXN];
int n, m;
bool check(int x)
{
LL sum = 0, t = 0;
int r = n, l = n - x + 1;
while(1)
{
int ans = -1;
int tl = l, tr = r;
while(tl <= tr)
{
int mid = (tl + tr) >> 1;
if(cs[mid] < t)
{
ans = mid;
tl = mid + 1;
}
else
tr = mid - 1;
}
if(ans == -1)
{
sum += lei[r] - lei[l - 1] - (r - l + 1) * t;
r = l - 1;
l = (r - x + 1) >= 1 ? (r - x + 1) : 1;
}
else
{
if(ans == r)
{
sum += 0;
break;
}
else
{
sum += lei[r] - lei[ans] - (r - ans) * t;
break;
}
}
t ++;
if(r < 1)
break;
}
if(sum >= m)
return 1;
else
return 0;
}
int main()
{
LL maxx = 0;
LL sum = 0;
scanf("%d %d", &n, &m);
for(int i = 1; i <= n; i ++)
{
scanf("%d", &cs[i]);
sum += cs[i];
maxx = max (maxx, cs[i]);
}
if(sum < m)
{
cout << -1 << endl;
return 0;
}
sort (cs + 1, cs + n + 1);
for(int i = 1; i <= n; i ++)
lei[i] = lei[i - 1] + cs[i];
//build(1, n, 1);
//cout << 1 << endl;
int l = 1, r = n, ans;
while(l <= r)
{
int mid = (l + r) >> 1;
//cout << "! " << mid << endl;
if(check(mid))
{
ans = mid;
r = mid - 1;
}
else
l = mid + 1;
}
printf("%d", ans);
return 0;
}