题目大意:
这道题目题意比较复杂,但是仔细分析以后模型还是比较简单的。。。
题意就是接下来有n个月,
ci di mi pi 分别表示第 i 个月的原材料价格,客户要求的计算机数量,mi表示生产一台计算机的人工成本,pi表示这个月能生产的计算机数量。
同时我们可以预先储存一些原材料和计算机,对于储存的原材料的数量没有限制,计算机数量有限制,并且储存需要一定的成本。
ei Ri Ei 分别表示能储存到下个月的计算机数量,储存一份原材料的成本和一台计算机的成本。
解题思路:
首先可以看到题目特别说明了对于储存的原材料的数量没有限制,那么我们无论位于哪个月其实都可以在之前任意一个月买,那么我们就挑一种总体成本最小的方式来买原材料。我们可以预处理出每个月买原材料最优的价格是多少替换 ci 即可。
这样我们就不需要注意 ci 和 Ri 这两个变量了。。。
然后就是计算机的问题,首先我们可以想到我们可以假设每次都尽量把仓库填满,这样可以保证我们没有浪费,我们计算成本的时候给客户以后再计算这台计算机产生的成本。那么流程就变为了,
首先对于一个新的月份,我们判断我们仓库中的电脑加上这个月能生产的电脑的总额是否满足要求,不满足就break。
满足的话,我们就从仓库中挑出成本最小的和当前这个月的生产成本比较,选择生产成本较小的给客户。
最后再将这个月多余的生产力添加到仓库中,添加到仓库中后判断是否满足下个月的储存容量的上限。将成本最高的从仓库中去除即可,那么如何维护这个仓库呢,
我们先想一下这个仓库需要实现什么功能,首先需要能够查询生产成本最小的电脑仓库里有多少台,其次需要知道生产成本最大的电脑仓库里有多少台。
这里有一个比较关键的点,就是电脑生产的成本看似很难统一,因为不同月生产的电脑储存成本不同,那么我们可以假设所有电脑都是第一个月生产的,即我们将它们加入到仓库的时候减去一个成本的前缀和,这样它们就被认为是在第一个月所生产的,处于同一起跑线,则可以正常的进行比较大小。
我这里用的是一棵权值线段树维护的,因为每个月电脑加入仓库的成本都是可以预先知道的,所以储存起来离散一下即可。感觉还是挺方便的,就是自己最后代码写的好恶心。。。比赛的时候一个傻逼地方没发现,赛后ac传统。。。
Ac代码:
#include<bits/stdc++.h>
#define lson rt<<1
#define rson rt<<1|1
using namespace std;
typedef long long ll;
const int maxn=1e5+5;
ll res=0;
ll c[maxn],d[maxn],m[maxn],p[maxn]; //原材料价格 客户需求 电脑成本 生产数量
ll e[maxn],R[maxn],E[maxn]; //电脑储存数量 原材料储存成本 电脑储存成本
ll sum[maxn]; //电脑储存成本前缀和
int n,mval,mcnt;
vector<ll> v; //离散每个月电脑成本
int getid(ll x) { return lower_bound(v.begin(),v.end(),x)-v.begin()+1; }
struct node
{
int l,r,mid;
int val; //储存每种成本电脑的数量
}t[maxn<<2];
void build(int l,int r,int rt) //建立权值线段树
{
int mid=(l+r)>>1;
t[rt].l=l,t[rt].r=r,t[rt].mid=mid;
t[rt].val=0;
if(l==r) return ;
build(l,mid,lson);
build(mid+1,r,rson);
}
void update(int pos,int val,int rt)
{
if(t[rt].l==t[rt].r)
{
t[rt].val+=val;
return ;
}
if(pos<=t[rt].mid) update(pos,val,lson);
if(pos>t[rt].mid) update(pos,val,rson);
t[rt].val=t[lson].val+t[rson].val;
}
void query(int flag,int rt) //flag>0 查最小值 反之查最大值
{
if(t[rt].l==t[rt].r)
{
mval=t[rt].l;
mcnt=t[rt].val;
return ;
}
if(flag>0)
{
if(t[lson].val) query(flag,lson);
else query(flag,rson);
}
else
{
if(t[rson].val) query(flag,rson);
else query(flag,lson);
}
}
int main()
{
int QAQ;
scanf("%d",&QAQ);
while(QAQ--)
{
v.clear(); res=0;
scanf("%d",&n);
for(int i=1;i<=n;i++)
scanf("%lld%lld%lld%lld",&c[i],&d[i],&m[i],&p[i]);
for(int i=1;i<n;i++)
{
scanf("%lld%lld%lld",&e[i],&R[i],&E[i]);
sum[i]=sum[i-1]+E[i]; //前缀和
}
ll x=c[1];
for(int i=2;i<=n;i++) //维护每个月买原材料的最小价值
{
c[i]=min(c[i],x+R[i-1]);
x=c[i];
}
for(int i=1;i<=n;i++)
v.push_back(m[i]+c[i]-sum[i-1]);
sort(v.begin(),v.end()),v.erase(unique(v.begin(),v.end()),v.end());
build(1,n,1);
int flag=0;
for(int i=1;i<=n;i++) //对于每个月判断
{
if(t[1].val+p[i]<d[i]) //不满足break
{
flag=1;
break;
}
int pos=getid(m[i]+c[i]-sum[i-1]); //假设将这个月的加入仓库 处理方便
update(pos,p[i],1);
while(d[i]) //不停挑最小的拿出来即可
{
mcnt=0,mval=0;
query(1,1);
ll val=v[mval-1]+sum[i-1];
if(d[i]>=mcnt)
{
res+=val*mcnt;
d[i]-=mcnt;
update(mval,-mcnt,1);
}
else
{
res+=val*d[i];
update(mval,-d[i],1);d[i]=0;
}
}
int tmp=t[1].val; //将仓库中多余电脑去掉
if(tmp>e[i]) tmp=tmp-e[i];
else tmp=0;
while(tmp)
{
mval=0,mcnt=0;
query(0,1);
if(mcnt>=tmp)
{
update(mval,-tmp,1);
tmp=0;
}
else
{
update(mval,-mcnt,1);
tmp-=mcnt;
}
}
}
if(flag) printf("-1\n");
else printf("%lld\n",res);
}
}