题解:AT_abc458_g [ABC458G] Children Yearn for the Evil Kindergarten
有趣的题。
思路
首先,容易发现答案有单调性,考虑二分答案,设当前二分到了 \(m\),问题就变成了判断能否让 \(m\) 个人逃离。
对其他的 \(10^{100}-m\) 个人,既然他们逃离不了,那让他们在第一天退出就行了,所以只用贪心地考虑 \(m\) 个人。设一个人在第 \(i\) 天退出的总花费是 \(V_j=\sum_{j=1}^{i} B_j+C_j\) ,那对于任意的 \(i\),如果存在 \(j>i,V_j\leq V_i\),那么 \(i\) 肯定是不优的,因为将第 \(i\) 天退出的所有人变成在第 \(j\) 天退出能让花费更少,且第 \(j\) 天的奖牌更多,不会出现奖牌不够的情况。最后能贡献的 \(i\) 对应的 \(V_i\) 一定是单增的,所以可以用单调栈维护 \(V_i\)。
然后直接从前往后贪心地考虑就行了……吗?实际上连样例都过不了,因为如果前面退出的人过多,会使剩下的奖牌数过少,不够剩下的人上交 \(B_i\) 个了。考虑反悔,从前面已经退出的人中选 \(V\) 最大的人重新加回来,直到满足条件。这看似需要用堆来维护,但实际上因为 \(V_i\) 已经是单增的了,所以选最近一次退出的人就行了,可以用栈维护,使复杂度少一个 \(\log\)。具体实现时需要把在同一天退出的人一起维护。
代码
#include <bits/stdc++.h>
using namespace std;
#define int long long
#define pii pair<int,int>
const int N=3e5+5;
int n,a[N],b[N],c[N],v[N],st1[N],flag[N],top;
pii st2[N];
bool check(int m)
{
int sum=0,res=0;
top=0;
for(int i=1;i<=n&&m;i++)
{
res+=a[i]-m*b[i];;
sum+=b[i];
while(res<0&&top)
{
pii p=st2[top--];
if(p.first<=sum)
return 0;
int k=min(p.second,(-res-1)/(p.first-sum)+1);
res+=k*(p.first-sum);
m+=k;
if(k<p.second)
st2[++top]={p.first,p.second-k};
}
if(res<0)
return 0;
if(flag[i])
{
int k=min(m,res/c[i]);
st2[++top]={sum+c[i],k};
res-=k*c[i];
m-=k;
}
}
if(m)
return 0;
return 1;
}
void solve()
{
top=0;
for(int i=1;i<=n;i++)
flag[i]=0;
scanf("%lld",&n);
for(int i=1;i<=n;i++)
{
scanf("%lld%lld%lld",&a[i],&b[i],&c[i]);
v[i]=v[i-1]-c[i-1]+b[i]+c[i];
while(top&&v[i]<=v[st1[top]])
top--;
st1[++top]=i;
}
for(int i=1;i<=top;i++)
flag[st1[i]]=1;
int l=0,r=1e9,ans=0;
while(l<=r)
{
int mid=(l+r)>>1;
if(check(mid))
{
l=mid+1;
ans=mid;
}
else
r=mid-1;
}
printf("%lld\n",ans);
}
signed main()
{
int T;
scanf("%lld",&T);
while(T--)
solve();
return 0;
}

浙公网安备 33010602011771号