2019牛客暑期多校训练营(第七场)C Governing sand(权值线段树)

题意:砍树,目的让最高的树数量大于所有树的数量。,砍树有代价,求最小代价

题解:对高度排序,枚举每个最高高度,第一部分肯定是删除大于枚举高度的的所有代价,第二步分就是在小于枚举高度所有树在删除足够的就可以了。所以用个数据结构,权值线段树找第K大,返回第K大时的花费就好了。

细节:仔细读题,题中给的是每种树的高度对应树代价和树的数量,所以会有同种高度但是代价不同的的情况。第二点就是注意边更新边查询,树中不能出现大于枚举高度的值。

#include<cstdio>
#include<cstring>
#include<string.h>
#include<algorithm>
#include<iostream>
#include<stdlib.h>
#include<vector>
using namespace std;
const int INF=0x3f3f3f3f;
const int maxn=2e5+10;
typedef long long ll;
const ll inf=0x3f3f3f3f3f3f3f;
ll sum[maxn*4];//该结点为这段区间的和
ll num[maxn*4];//该结点的某个数的权值(出现次数)
void build(int l,int r,int p){
    if(l==r){
        sum[p]=0;
        num[p]=0;
        return ;
    }
    int mid=(l+r)/2;
    build(l,mid,p<<1);
    build(mid+1,r,p<<1|1);
}
void update(int l,int r,int pos,int p,ll nm){
    if(l==r){
        sum[p]+=pos*nm;
        num[p]+=nm;
        return ;
    }
    int mid=(l+r)/2;
    if(pos<=mid) update(l,mid,pos,p<<1,nm);
    else         update(mid+1,r,pos,p<<1|1,nm);
    sum[p]=sum[p<<1]+sum[p<<1|1];
    num[p]=num[p<<1]+num[p<<1|1];
}
ll query(int l,int r,ll k,int p){//优先左子树找
    if(l==r){
        ll kk=min(num[p],k);
        return kk*l;
    }
    int mid=(l+r)/2;
    ll Lnum=num[p<<1];
    if(Lnum>=k){//如果左子树和已经大于要凑的数,直接去左子树
        return query(l,mid,k,p<<1);
    }else{//负责那一定是左子树所有数出现的次数+右子树的情况
        return sum[p<<1]+query(mid+1,r,k-Lnum,p<<1|1);
    }
}
struct node{
   ll h,w,num;
}s[maxn];
bool cmp(node a,node b){
    return a.h<b.h;
}
ll presum[maxn];//花费
ll pre[maxn];//树的个数
int main(){
    std::ios::sync_with_stdio(false);
    int n;
    while(~scanf("%d",&n)){
        memset(num,0,sizeof(num));
        memset(sum,0,sizeof(sum));
        int N=200;
        build(1,N,1);
        ll chk=0;
        for(int i=1;i<=n;i++){
            scanf("%lld%lld%lld",&s[i].h,&s[i].w,&s[i].num);
            chk+=s[i].num;
        }
        sort(s+1,s+n+1,cmp);
        for(int i=1;i<=n;i++){
            presum[i]=presum[i-1]+s[i].num*s[i].w;
            pre[i]=pre[i-1]+s[i].num;
        }
        ll ans=inf;
        for(int i = 1; i <= n; i++){
            ll hh = s[i].h;
            ll nn = s[i].num;
            int flag = 0;
            int j = i+1;
            for(; j <= n; j++){
                if(hh == s[j].h){
                    nn += s[j].num;
                    flag = 1;
                }
                else break;
            }
            j--;
            ll ans_2 = presum[n] - presum[j];
            ll cnt_2 = pre[n] - pre[j];
            if(nn > ((chk - cnt_2 + 1)/2)) ans = min(ans, ans_2);
            else{
                ll p = chk-cnt_2;
                ll k = (p - nn) - nn + 1;
                ll ans_1 = query(1, N, k, 1);
                ans = min(ans_1 + ans_2, ans);
            }
            for(int pp=i;pp<=j;pp++){
            	update(1, N, s[pp].w, 1, s[pp].num);
			}
            if(flag) i = j;
         }
         printf("%lld\n", ans);
    }
    return 0;
}
/*
3
3 20 1
2 10 3
4 5 4
3
4 100 2
4 100 4
4 100 3
*/

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值