jzoj5043 【NOI2017模拟4.4】保持平衡 (可撤销贪心)

题意

博爱路上种起了一棵棵的大树,但是有一些地方的树超过了负荷,有一些地方的树的数量又不够。
我们不妨把博爱路看做一条数轴,数轴有n个点,从1到n编号,第i个位置原来现在有ai棵树,这个位置的需求是bi棵树。ai,bi都是0到10的整数。由于你需要是这个位置的树的数量保持平衡,所以你需要移除或者搬一些树过来。
我们怎么使树的数量平衡呢?
首先,你可以从某个位置i移动一棵树到位置j,这时,你需要的运费是|i-j|*z元。
其次,你可以从商店买一棵树,需要支付x元,这时商店会把树配送到任意位置。
还有就是,你可以叫别人收购在任意位置一棵树,需要支付y元运费。
问使得树的数量平衡最小需要支付多少钱?

分析

之所以写这题的博客,是因为这题代表了一类算法:可撤销贪心。
首先不难发现一个结论,如果一个点有进也有出那么可以合并这两个操作使得这个点只进或只出。

开两个小根堆need与give。堆中每一个点代表着对于每个点入一个或出一个的代价。
移动的代价是 |ij|z ,已知是往后移或往前移所以可以拆成两个点的部分。
每次碰到一个a[i]>b[i],在need堆中取出最小的,与y比较。 然后得出给i运出一个的最小代价cost,给答案加上cost并将 -cost-i*z放入give堆以便给后面需要运入的地方选择。重复上述步骤a[i]-b[i]次。

对于a[i]< b[i]基本同理。

放入give堆的权值是-cost-i*z的意义是,从这个点拿出一个时,可以先撤销先前运出的操作。会不会影响到最开始被i处理的位置j?
因为这个权值实际上还是被加在了答案中。设最开始处理j的代价是c1,在i用它的时候c1被减去了,但i的放入堆的-cost中就包含这个c1(取了两次负),所以没有问题。

此题还有复杂度高的上下界费用流与经典的双序列dp解法,甚至线性解法也有,参见
http://blog.csdn.net/jokerwyt/article/details/77170781

Code

#include 
#include 
#include 
#define N 100010
using namespace std;
typedef long long ll;
priority_queuevector, greater > need,noneed;
ll n,x,y,z,a[N],b[N],ans;
int main() {
    freopen("balance.in","r",stdin);
    freopen("balance.out","w",stdout);
    cin>>n>>x>>y>>z;
    for (int i=1; i<=n; i++) scanf("%lld %ld",&a[i],&b[i]);
    for (int i=1; i<=n; i++) {
        int cost=0;
        if (a[i]>b[i]) {
            for (; a[i]!=b[i]; --a[i]) {
                if (!need.empty() && (cost=need.top()+i*z)<=y) need.pop();
                else cost=y;
                noneed.push(-cost-i*z);
                ans+=cost;
            }
        } else {
            for (; a[i]!=b[i]; ++a[i]) {
                if (!noneed.empty() && (cost=noneed.top()+i*z)<=x) noneed.pop();
                else cost=x;
                need.push(-cost-i*z);
                ans+=cost;
            }
        }
    }
    cout<

你可能感兴趣的:(题解,贪心)