【CF505E】Mr. Kitayuta vs. Bamboos(逆向思维)
- 给定两个长度为\(n\)的序列\(h\)和\(a\),你需要进行\(m\)轮操作:
- 进行\(k\)次修改,每次选择将一个\(h_i\)修改为\(\max\{h_i-p,0\}\)。
- 所有\(h_i\)加上\(a_i\)。
- 求最终\(h_i\)中最大值的最小值。
- \(n\le10^5,m\le5\times10^3,k\le10\)
二分答案+逆向思维
最小化最大值,显然二分答案\(x\)。
显然\(\max\{h_i-p,0\}\)一看就是个非常麻烦的东西,因此我们考虑倒着做。
假设最终所有数都变成了\(x\)。
现在就是每次可以进行加\(p\)操作,每轮所有数会减去各自的\(a_i\)。
要求满足所有数始终大于等于\(0\),且最终所有数要大于各自的\(h_i\)。
贪心+桶优化
因为此时加\(p\)操作没有限制了,所以我们可以选择把之前的操作先屯着,每次不得不用的时候再用,显然一定不会劣。
所以我们求出每一个数最多能撑过几次操作后会小于\(0\),然后把它扔到这个次数对应的桶里面。
然后依次枚举所有桶,每次取出桶中元素,给它加\(p\)让它大于等于\(0\),接着重新计算能撑过几次操作,扔到新的桶里。
显然\(i\)轮操作时操作次数超过\(i\times k\)可以直接返回\(false\),故单次检验复杂度是\(O(mk)\)的。
代码:\(O((n+mk)logAns)\)
#include<bits/stdc++.h>
#define Tp template<typename Ty>
#define Ts template<typename Ty,typename... Ar>
#define Reg register
#define RI Reg int
#define Con const
#define CI Con int&
#define I inline
#define W while
#define N 100000
#define LL long long
#define mp make_pair
#define Pr pair<LL,LL>
#define fi first
#define se second
using namespace std;
int n,m,k,p,h[N+5],a[N+5];vector<int> V[N+5];vector<int>::iterator it;
LL s[N+5];I bool Check(Con LL& x)//验证答案
{
#define Push(id,x) (id)<m&&(V[id].push_back(x),0)
RI i;for(i=0;i^m;++i) V[i].clear();for(i=1;i<=n;++i) Push((s[i]=x)/a[i],i);//初始化桶
RI t=0;LL d;for(i=0;i^m;++i) for(it=V[i].begin();it!=V[i].end();++it)//依次枚举所有桶,遍历桶
{
d=(1LL*a[*it]*(i+1)-s[*it]+p-1)/p;//计算需要加多少次,注意上取整
if(s[*it]+=d*p,(t+=d)>i*k) return false;Push(s[*it]/a[*it],*it);//重新计算能撑几次,扔到新的桶里
}
for(i=1;i<=n;++i) if(s[i]<1LL*a[i]*m+h[i])//每个数最终要大于h[i]
if(d=(1LL*a[i]*m+h[i]-s[i]+p-1)/p,(t+=d)>m*k) return false;return true;
}
int main()
{
RI i;for(scanf("%d%d%d%d",&n,&m,&k,&p),i=1;i<=n;++i) scanf("%d%d",h+i,a+i);
LL l=0,r=1e13,mid;W(l^r) Check(mid=l+r>>1)?r=mid:l=mid+1;return printf("%lld\n",r),0;//二分答案
}
待到再迷茫时回头望,所有脚印会发出光芒