解题思路
二分枚举+检验的典型例题。
首先考虑边界问题,最大的情况是 的快乐值,但是实际上可以在读入的时候就对巧克力的快乐值进行累计,这样得到的就是可能存在的最大解,因为每过一天快乐值都会减半所以最大值不会超过巧克力快乐值总数;
其次是二分的写法问题,题卡了好几天换了好几种写法最后选择了完全抛弃 的写法,目标是让最小快乐值最大,相当于找 的第一个位置,最后判断语句为真的 就是答案, 语句内的判断就是检验过程,按时间循环,每天的开始当前快乐值减半,然后低于传入的二分答案就吃巧克力,同时标记巧克力是哪天吃的,吃巧克力的动作只要巧克力还有并且快乐值未达到二分答案就需要继续进行,那么当巧克力不够吃了就说明二分答案取大了,需要左移右边界,当天数进行完循环才结束的时候就说明现在的方案是足够满足目前的二分答案的,所以更新 ,还有可能还能再大一点,右移左边界继续进行尝试,直到右边界小于左边界二分结束;
最后是确定答案的一步,因为在上一步二分找答案的过程中,循环结束的状态数组里存的并不是最终方案,需要用正确答案再走一次,需要注意的是,如果最终答案里巧克力没吃完,那么就要在最后一天把巧克力都吃掉,也就是 数组将剩下没吃的都标记为最后一天吃掉。
本题的小细节比较多,一个比较隐蔽的是对所吃巧克力的计数标记 ,因为 会在检验函数和主函数内都用到,所以定义成全局变量比较方便,再就是一定要注意题目是求最小值最大还是最大值最小。
通过代码
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
ll n, d, j;
ll h[50050];
ll vis[50050];
bool deal(ll x)
{
ll cur = 0;
j = 1;
for (int i = 1; i <= d; i++)
{
cur >>= 1;
while (cur < x && j <= n)
{
cur += h[j];
vis[j] = i;
j++;
}
if (cur < x) return false;//取大了
}
return true; //现有巧克力可以满足要求,可以更大一点
}
int main()
{
ll l = 1, r, mid, ans = 0;
cin >> n >> d;
for (int i = 1; i <= n; i++){
scanf("%d", &h[i]);
r += h[i];
}
while (l <= r)
{
mid = (l + r) / 2;
if (deal(mid))
{
ans = mid;
l = mid + 1;
}
else
r = mid - 1;
}
deal(ans);
for (int i = j; i <= n; i++)
vis[i] = d;
cout << ans << endl;
for (int i = 1; i <= n; i++)
cout << vis[i] << endl;
return 0;
}