把博客园图标替换成自己的图标
把博客园图标替换成自己的图标end

【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;//二分答案
}
posted @ 2020-11-17 21:21  TheLostWeak  阅读(95)  评论(0编辑  收藏  举报