题目:
有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 <bits/stdc++.h>
using namespace std;
const int N = 1e6+10;
long long sz[N<<2];
long long sum[N<<2];
vector<pair<int,int>>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;
}