2019牛客暑期多校训练营(第九场)H-Cutting Bamboos(主席树)

本文介绍了一种使用主席树和二分搜索解决复杂竹子砍伐问题的方法。该问题涉及在多次独立询问中,根据指定次数和区间砍伐竹子,目标是确定特定砍伐次数下的竹子高度。通过维护砍伐后的竹子高度总和,并利用主席树进行高效查询,结合二分搜索找到满足条件的高度。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

题意:n个竹子,有高度,q次询问,询问之间是独立的,每次查询输入l,r,x,yl,r,x,yl,r,x,y代表砍区间[l,r][l,r][l,r]内的竹子砍y次,最后一次要砍成0,每次砍掉的总长度相同,问第x次砍的高度是多少。

赛中榜有点歪吧,就一直在想j怎么写,H也没怎么看,最后剩20分钟的时候,lt跑过来和我说这个好像可以二分做,然后我就想可以套主席树写,就赶紧写,结果第一次写完的时候少维护了一个东西,然后就快5点了,我写完正好5:01,交了就过了2333。

既然每次要求砍掉的东西都相同,那么就可以直接算出来砍第x次需要砍掉多少,然后只需要二分这个高度,在主席树中查找大于等于这个高度的竹子总和减去个数乘以高度即可。

#include<bits/stdc++.h>
using namespace std;

typedef long long ll;

const int maxn=2e5+7;
const double epos=1.0e-7;

int a[maxn];
int b[maxn];
int m;

void quchong(int n){
    sort(b+1,b+1+n);
    m=unique(b+1,+b+1+n)-b-1;
}

int getid(int x){
    return lower_bound(b+1,b+1+m,x)-b;
}

struct Tree{
    int lc,rc;
    ll sum;
    ll x;
}tree[maxn*50];

int root[maxn],tot;

int build(int l,int r){
    int k=++tot;
    tree[k].sum=0;
    tree[k].x=0;
    if(l==r) return k;
    int mid=(l+r)>>1;
    tree[k].lc=build(l,mid);
    tree[k].rc=build(mid+1,r);
    return k;
}

void pushup(int k){
    tree[k].sum=tree[tree[k].lc].sum+tree[tree[k].rc].sum;
    tree[k].x=tree[tree[k].lc].x+tree[tree[k].rc].x;
}

int updata(int now,int l,int r,int id,int val){
    int k=++tot;
    tree[k]=tree[now];
    if(l==r){
        tree[k].sum+=val;
        tree[k].x++;
        return k;
    }
    int mid=(l+r)>>1;
    if(id<=mid) tree[k].lc=updata(tree[now].lc,l,mid,id,val);
    else tree[k].rc=updata(tree[now].rc,mid+1,r,id,val);
    pushup(k);
    return k;
}

ll myfind(int p,int q,int l,int r,int L,int R){
    if(l>=L&&r<=R) return tree[p].sum-tree[q].sum;
    int mid=(l+r)>>1;
    ll res=0;
    if(L<=mid) res+=myfind(tree[p].lc,tree[q].lc,l,mid,L,R);
    if(R>mid) res+=myfind(tree[p].rc,tree[q].rc,mid+1,r,L,R);
    return res;
}

ll myfind1(int p,int q,int l,int r,int L,int R){
    if(l>=L&&r<=R) return tree[p].x-tree[q].x;
    int mid=(l+r)>>1;
    ll res=0;
    if(L<=mid) res+=myfind1(tree[p].lc,tree[q].lc,l,mid,L,R);
    if(R>mid) res+=myfind1(tree[p].rc,tree[q].rc,mid+1,r,L,R);
    return res;
}

double check(int l,int r,double x){
    double res=0;
    int id=getid(ceil(x));
    if(id==m+1) return 0;
    return myfind(root[r],root[l-1],1,m,id,m)-myfind1(root[r],root[l-1],1,m,id,m)*x;
}

ll sum[maxn];

int main(){
    int n,q;
    scanf("%d%d",&n,&q);
    for(int i=1;i<=n;++i){
        scanf("%d",&a[i]);
        sum[i]=sum[i-1]+a[i];
        b[i]=a[i];
    }
    quchong(n);
    root[0]=build(1,m);
    for(int i=1;i<=n;++i)
        root[i]=updata(root[i-1],1,m,getid(a[i]),a[i]);
    int ll,rr,x,y;
    double l,r,mid;

    while(q--){
        scanf("%d%d%d%d",&ll,&rr,&x,&y);
        double zong=(sum[rr]-sum[ll-1])*1.0/y*x;
        l=0,r=1000000000;
        while(r-l>epos){
            mid=(l+r)/2;
            double xx=check(ll,rr,mid);
            if(xx>zong) l=mid;
            else r=mid;
        }
        printf("%.10f\n",r);
    }

    return 0;
}
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值