题意:砍竹子,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;
}