Description
农夫约翰正在建造一个美丽的花园,在这个过程中需要移动大量的泥土。花园由N个花圃(1≤N≤100,000)组成,
第i个花圃最开始有Ai个泥土。 农夫约翰想要重新整理花园,使每个花圃最后有Bi个泥土。Ai和Bi都是0…10范围
内的整数。为了整理花园,Farmer John有几个选择:他可以购买一个单位的泥土,并将它放在他选择的花圃中,
用X单位的钱。 他可以从他选择的花圃上清除一块泥土,并用Y单位的钱运出去。他还可以用Z*|i-j|的花费将一单
位的泥土从花圃i运输到花圃j。请计算农民约翰完成他的绿化项目的最低总成本。
0≤X,Y≤10^8; 0≤Z≤1000
Solution
做法非常奇妙
注意到泥土的总数不大,那么可以精确地分配每一单位泥土的去向。
用两个堆h1和h2表示空缺的泥土和多出的泥土
如果要在当前位置i购买一个单位的泥土,就在h2插入i*Z+X,对答案的贡献是Z
如果要在当前位置i清除一个单位的泥土,就在h1插入i*Z+Y,对答案的贡献是Y
如果要从前i个位置中移走一个单位泥土到位置i,就在h2插入i*Z*2-h1.top(),对答案的贡献是i*Z-h1.top(),弹出h1.top()
反之亦然
每一单位泥土都贪心一下
考虑这样做的正确性:|i-j|*Z可以只考虑i< j的情况,也就是i*Z-j*Z。我们插入的每一单位泥土都可以移动到更后的位置,代价柿子展开化简后正好是实际移动的代价
注意两个堆为空的情况和记得开ll
Code
#include <stdio.h>
#include <string.h>
#include <queue>
#define rep(i,st,ed) for (int i=st;i<=ed;++i)
typedef long long LL;
const int N=200005;
int t[N];
std:: priority_queue <LL> h1,h2;
int main(void) {
freopen("data.in","r",stdin);
freopen("myp.out","w",stdout);
int n; LL X,Y,Z; scanf("%d%lld%lld%lld",&n,&X,&Y,&Z);
rep(i,1,n) {
int a,b; scanf("%d%d",&a,&b);
t[i]=a-b;
}
LL ans=0;
for (LL i=1;i<=n;i++) {
if (t[i]>0) {
rep(j,1,t[i]) {
if (h1.empty()) {
ans+=Y;
h2.push(i*Z+Y);
} else {
if (Y<=i*Z-h1.top()) {
ans+=Y;
h2.push(i*Z+Y);
} else {
ans+=i*Z-h1.top(); h2.push(i*Z*2-h1.top());
h1.pop();
}
}
}
} else if (t[i]<0) {
rep(j,1,-t[i]) {
if (h2.empty()) {
ans+=X;
h1.push(i*Z+X);
} else {
if (X<=i*Z-h2.top()) {
ans+=X;
h1.push(i*Z+X);
} else {
ans+=i*Z-h2.top(); h1.push(i*Z*2-h2.top());
h2.pop();
}
}
}
}
}
printf("%lld\n", ans);
return 0;
}