2018-2019 ICPC, NEERC, CodeForces 1070C Cloud Computing(权值线段树)

题目:

有n天,每天需要k个cpu。现在有m个供应商,第i个供应商可在[li,ri]天之间,每天提供ci个cpu,每个cpu价格为pi。
如果某一天的cpu无论如何也凑不够,那就把仅有的全用上。问n天的最小花费。

分析:

最开始的想法是,把供应商按价格排序,然后依次取供应商去提供一个时间段的cpu。在线段树上想办法把这个时间段做一个区间减法。但发现其实在线段树上很难操作,很难知道供应商提供的区间哪天需要哪天不需要。
正解是,对价格建立一颗权值线段树,每个节点表示这个价格区间内可用的cpu有几个,这样在某一天去树中查询时,我们就可找到价格最低的k个cpu。在维护线段树时,额外再维护一个这些cpu的价格,方便直接用。所以,线段树的每个节点存的是这个价格区间内的cpu数量和总价。

现在的问题就是如何在第i天去树中查询时保证树里的cpu都是可用的。解决方法是,在第i天,将第i天开始的供应商的数量和价格插入树中。同时,将这一天结束的(不再提供cpu)供应商数量取相反数,也插入树中,这样相当于把之前插入的从树中拿了出来。比如某个供应商可在[l,r]提供,那么在第 l 天往树里插入(p,c),第r+1天往树里插入(p,-c);

权值线段树在解决这种需要每次取某个权值最小的问题上很管用,通常二分+树状数组也可以。

代码:

#include 
using namespace std;
const int N = 1e6+10;
long long sz[N<<2];
long long sum[N<<2];
vector>V[N];
void Insert(int p,int L,int R,int pos,int val){
    sz[p]+=val;
    sum[p]+=(long long)pos*val;
    if (L==R)return ;
    int mid=(L+R)>>1;
    if (pos<=mid) Insert(p<<1,L,mid,pos,val);
    else Insert(p<<1|1,mid+1,R,pos,val);
}
long long getSum(int p,int L,int R,int g){
    if (L==R) return min((long long)g,sz[p])*L;
    int mid=(L+R)>>1;
    long long ret=0;
    if (g<=sz[p<<1]) ret=getSum(p<<1,L,mid,g);
    else{
        ret=sum[p<<1]+getSum(p<<1|1,mid+1,R,g-sz[p<<1]);
    }
    return ret;
}
int main(){
    int n,m,k;scanf("%d%d%d",&n,&k,&m);
    int maxc = 0;
    for (int i=1;i<=m;i++){
        int u,v,c,p;scanf("%d%d%d%d",&u,&v,&c,&p);//p price
        V[u].emplace_back(p,c);
        V[v+1].emplace_back(p,-c);
        maxc=max(maxc, p);
    }
    long long ans=0;
    for (int i=1;i<=n;i++){
        for (auto &temp: V[i]){
            int p=temp.first, c=temp.second;
            Insert(1,1,maxc,p,c);
        }
        ans += getSum(1,1,maxc,k);
        //printf("%d %I64d\n",i,ans);
    }
    printf("%I64d\n",ans);
    return 0;
}

你可能感兴趣的:(线段树)