2019牛客暑期多校训练营(第九场)H.Cutting Bamboos(二分答案+主席树)

题意:砍竹子,n个高度,Q次询问,每次给你{l,r,x,y},让你在区间【l,r】内砍Y次,问你第X次砍到了多少高度(h),每次砍了的总长度相等

题解:由于可以算出第X次砍完后的,砍了的高度,所以可以通过二分答案来得到砍到了多少高度。

           所以 第X次砍了的高(H)就是==

           高于这个高度的所有竹子的高度和 (HSUM) -- 高与这个高度的所有竹子个数NUM * 第x次砍完后的高度(h)

          主席树可以快速算出高于这个高度的所有竹子的高度和,和高与这个高度的所有竹子个数。

细节:建树是按高度整数建的,但是二分答案,是小数的,所以需要 hh=ceil(h)得到 HSUM 但是 NUM需要乘上小数的来减少误差。

PS.主席树写查询时可以按照线段树查询写,好想一些,就相当于是在主序树的权值区间内进行线段树查询,刚开始按第K大那样写,老写不对....

#include <bits/stdc++.h>
#define ll long long
using namespace std;
const int maxn = 1e6 + 5;
const int INF = 0x3f3f3f3f;
const ll MOD = 1e9 + 7;
const double eps=1.0e-7;
const int N=1e5+5;
struct node{
	int l;
	int r;
	ll num;//个数 
	ll sum;//区间和 
}T[maxn*50];
int a[maxn],root[maxn],cnt;
void update(int l,int r,int &x,int y,int pos){
	T[++cnt]=T[y];
	T[cnt].num++; T[cnt].sum+=pos;   
	x=cnt;       
	if(l==r) return ;
	int mid=(l+r)/2;
	if(pos<=mid) update(l,mid,T[x].l,T[y].l,pos);
	else update(mid+1,r,T[x].r,T[y].r,pos);
}
ll querysum(int l,int r,int x,int y,int pl,int pr){
	if(pl<=l&&r<=pr)  return T[x].sum-T[y].sum;
	int mid=(l+r)/2;
	ll res=0;
	if(pl<=mid) res+=querysum(l,mid,T[x].l,T[y].l,pl,pr);
	if(pr>mid) res+=querysum(mid+1,r,T[x].r,T[y].r,pl,pr);
	return res;
}
ll querynum(int l,int r,int x,int y,int pl,int pr){
	if(pl<=l&&r<=pr) return T[x].num-T[y].num;
	int mid=(l+r)/2;
	ll res=0;
	if(pl<=mid) res+=querynum(l,mid,T[x].l,T[y].l,pl,pr);
	if(pr>mid) res+=querynum(mid+1,r,T[x].r,T[y].r,pl,pr);
	return res;
}
bool check(int l,int r,double h,double cnt_k){
	int hh=ceil(h); //cout<<"砍这个高度以上的: "<<hh<<endl;//砍的的大于他一个单位的 
//  double tmp_1=querysum(1,N,root[r],root[l-1],hh,N);
//	double tmp_2=querynum(1,N,root[r],root[l-1],hh,N);
	double tmp=querysum(1,N,root[r],root[l-1],hh,N)-querynum(1,N,root[r],root[l-1],hh,N)*h;
//	cout<<tmp_1<<" "<<tmp_2<<" "<<tmp<<endl;
    if(tmp<=cnt_k) return true;
    else return false;
}
ll  pre[maxn];
int main(){
	int n,q; 
	scanf("%d%d",&n,&q);
	for(int i=1;i<=n;i++){
		scanf("%d",&a[i]);
		pre[i]=pre[i-1]+a[i];
		update(1,N,root[i],root[i-1],a[i]);
	}
	while(q--){
		int l,r,x,y; 
		scanf("%d%d%d%d",&l,&r,&x,&y);
		double cnt_k=(pre[r]-pre[l-1])*1.0/y*x;//平均每次都得砍这么多
	//	printf("x次得砍这么多 %.5lf\n",cnt_k);   
		
		double L=0; double R=1000000000;
		while(L+eps<R){//二分每次砍的 
			double mid=(L+R)/2;
			if(check(l,r,mid,cnt_k)){//找第X次砍后的高度 
				R=mid;
			}else {
				L=mid;
			}
		}
		printf("%.10lf\n",R);
	}
	return 0;
} 

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值