【CF505E】Mr. Kitayuta vs. Bamboos(逆向思维)

点此看题面

  • 给定两个长度为(n)的序列(h)(a),你需要进行(m)轮操作:
    • 进行(k)次修改,每次选择将一个(h_i)修改为(max{h_i-p,0})
    • 所有(h_i)加上(a_i)
  • 求最终(h_i)中最大值的最小值。
  • (nle10^5,mle5 imes10^3,kle10)

二分答案+逆向思维

最小化最大值,显然二分答案(x)

显然(max{h_i-p,0})一看就是个非常麻烦的东西,因此我们考虑倒着做。

假设最终所有数都变成了(x)

现在就是每次可以进行加(p)操作,每轮所有数会减去各自的(a_i)

要求满足所有数始终大于等于(0),且最终所有数要大于各自的(h_i)

贪心+桶优化

因为此时加(p)操作没有限制了,所以我们可以选择把之前的操作先屯着,每次不得不用的时候再用,显然一定不会劣。

所以我们求出每一个数最多能撑过几次操作后会小于(0),然后把它扔到这个次数对应的桶里面。

然后依次枚举所有桶,每次取出桶中元素,给它加(p)让它大于等于(0),接着重新计算能撑过几次操作,扔到新的桶里。

显然(i)轮操作时操作次数超过(i imes 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
",r),0;//二分答案
}
原文地址:https://www.cnblogs.com/chenxiaoran666/p/CF505E.html