C-Balanced Stone Heaps
-
题目描述:给定\(n\)堆石子,规定第\(i(3\le i\le n)\)堆石子能够能够从中取出\(3d\)个石子(\(0<3d<h_i\),其中\(h_i\)为第\(i\)堆石子的数量),其中\(d\)个石子放入第\(i-1\)堆,\(2d\)个石子放入第\(i-2\)堆石子之中,我们从第\(3\)个石子往第\(i\)个石子遍历一次,问所有堆中石子数目最少的堆的石子数目最多是多少
- 求解“最少中最多”和“最多中最少”问题,我们显然可以联想到二分答案,而本题的关键之处便是在于如何对枚举的答案进行检验
- 正向考虑这个问题,对于第\(i(2\le i\le n-2)\)个位置,我们总是有两个来源,而如何从这两个来源中进行分配,,以拼凑出当前枚举的答案\(x\)相对较难考虑
- 所以我们逆向考虑这个问题,从第\(n\)个元素向前考虑, 每个元素扣除一定份额向前分配,要匹配枚举出的答案\(x\)相对容易,所以我们在检验答案时逆向考虑
- 而逆向考虑还有一个注意点,对于当前位置\(i\),我们有原本石子数\(h_i\)和增量\(b_i\),由于我们是自前向后遍历,所以我们只能利用原本石子数\(h_i\)来向前分配,而增量是无法用以向前分配的
- 所以我们在\(b_i\)小于\(x\)时,我们判断\(h_i+b_i\)是否大于枚举答案\(x\),当大于时,我们从\(h_i\)中扣除\(x-b_i\)部分使得其能满足标准答案\(x\),剩余\(h_i\)部分尽可能进行分配
- 如果是小于,我们可以直接认为这个答案不可取,因为对于第\(i\)个元素已经不可能满足,直接\(break\)即可
- 最后因第\(1,2\)个元素无需向前分配,对其作特殊判断即可
- 本题中还有一个关键要点,增量\(b_i\)如果使用数组进行存储的话,每次检查时都需要\(memset\),这会导致较大的时间复杂度,并在第二个测试样例超时,所以我们使用向量来存储增量,并在每次检查时根据元素数量进行定义
- 参考代码:
#include<bits/stdc++.h>
using namespace std;
const int maxn =2e5+5;
#define ll long long
// 二分答案
// 如何进行检验?
// 检验方法是本道题的关键
int T,n;
ll a[maxn];
int check(int x){
int flag=0;
vector<int> b(n+10);
// 本题卡memset,
for(int i=n;i>=3;i--){
if(b[i]>=x){
ll t=a[i]/3;
b[i-1]+=t;
b[i-2]+=2*t;
}else if(a[i]+b[i]>=x){
ll t=(a[i]-(x-b[i]))/3;
b[i-1]+=t;
b[i-2]+=2*t;
}else{
flag=1;
break;
}
}
if(a[1]+b[1]<x||a[2]+b[2]<x)flag=1;
return !flag;
}
int main(){
cin>>T;
while(T--){
cin>>n;
ll minx=1e9,maxx=0;
for(int i=1;i<=n;i++){
scanf("%lld",&a[i]);
minx=min(minx,a[i]);
maxx=max(maxx,a[i]);
}
ll l=minx,r=maxx;
ll ans;
while(l<=r){
ll mid=(l+r)>>1;
if(check(mid)){
ans=mid;
l=mid+1;
}else r=mid-1;
}
cout<<ans<<endl;
}
return 0;
}