笔记 - 算法基础 - 延迟决策的贪心问题

这一类的问题的共同特点是收到了一个名额之后不急着把名额用出去,先保留着名额,当后面遍历到不可抗力因素时把再把最差的若干个名额丢弃。注意有时候同一个名额可能多次收到,有可能以最后一次收到时为准(当然也可以多次出入队,不过这样要在出队时判断是否是一次有效的出队)。

还有CCPC的摸鱼大师那题啥时候去补一下。

https://www.cnblogs.com/KisekiPurin2019/p/11823903.html

https://codeforces.com/contest/1271/problem/D

D - Portals

题意:你要去征服直线上的按顺序的n个城堡,征服第i个城堡需要拥有至少ai个战士(只是需要拥有,并不会消耗),占领完成之后会获得bi个战士。你可以留下一个战士来驻守城堡,驻守会获得ci的得分。有两种方式驻守:你在i点,那么可以派人驻守i点;或者你在u点,u->v有传送门,那么你可以派人驻守v点。传送门只会通向前面的城堡。不能走回头路,假如未能占领完则输掉。求最大的得分,注意在占领完n号城堡后也可以进行驻守和使用n点的传送门。

题解:连题意都暗示要贪心?看起来很像CCPC网络赛的摸鱼宗师那道题。首先全部不进行驻守强行占领看看是不是有解。然后每一步都会有一个多余值,多余值的最小值就是可以拿去派的,派给全局得分最高的城堡就行了。然后全场的多余值都下降了,会有某个碰到了0,假如最后一个碰到0的城堡后面的城堡还有多余值,那么可以派给这一段城堡能够连向的地方的最高得分。也就是每次是询问一个包含结尾的城堡集及其传送门包含的城堡集里面的最大值。

那么每打一个城堡就把兵留下来并尽可能派往传送门,然后把这些兵放进小根堆里面等待召回,假如需要更多兵的时候就要召回他并清除城堡的得分及占领标记。注意到每个城堡最多收回5000个兵然后派出5000个兵,只有5000个城堡,再套个log就比较爆炸。

但是这个算法很有问题,打了补丁也还是很有问题,问题在于每次贪心取城堡,但是有时候把好的城堡空出来给后面的传送门拿更优。

就算不急着把城堡拿去驻守,那么在士兵不够用的情况肯定要去除一部分驻守的名额,要去除哪些呢?去除最小的显然不对,因为去除大的也有可能后面会有机会重新派过去驻守而那时候有空闲的士兵。发现一个很显然的贪心,派去驻守同一个城堡的时间越靠后越好,所以每个城堡实际上只有最后的指向它的传送门有效。在那个传送门时假如没有空闲的士兵就再也没有机会了。但是我们可以先保持这个城堡的名额,只有当人手不够的时候才会有城堡因为价值最小且目前持有的城堡以后都没有办法再驻守而被淘汰。反向遍历可以计算出每个点时空闲的士兵的数量,会发现其实过了某些险峻的关卡只会空闲度会突然上升。

int a[5005], b[5005], c[5005];
int prefixb[5005], dif[5005];
int from[5005];
vector<int> to[5005];
priority_queue<pii, vector<pii>, greater<pii> >pq;

void test_case() {
    int n, m, k;
    scanf("%d%d%d", &n, &m, &k);
    for(int i = 1; i <= n; ++i) {
        scanf("%d%d%d", &a[i], &b[i], &c[i]);
        from[i] = i;
    }
    for(int i = 1, u, v; i <= m; ++i) {
        scanf("%d%d", &u, &v);
        from[v] = max(from[v], u);
    }
    for(int i = 1; i <= n; ++i)
        to[from[i]].push_back(i);
    for(int i = 1; i <= n; ++i) {
        prefixb[i] = b[i] + prefixb[i - 1];
        dif[i] = k + prefixb[i] - a[i + 1];
    }
    for(int i = n - 1; i >= 1; --i)
        dif[i] = min(dif[i], dif[i + 1]);
    if(dif[1] < 0) {
        puts("-1");
        return;
    }
    for(int i = 1; i <= n; ++i) {
        for(auto &v : to[i])
            pq.push({c[v], v});
        while(pq.size() > dif[i])
            pq.pop();
    }
    int sum = 0;
    while(pq.size()) {
        sum += pq.top().first;
        pq.pop();
    }
    printf("%d\n", sum);
}

总结:这类问题不是要拿到城堡的时候立刻按照价值贪心,因为后面有可能会重新更新大的城堡所以有可能现在去除大的城堡更优。先观察出是只有最后一次机会派往城堡时才需要把他派出去,在这之前肯定是带着士兵一起走最好。就算拿到了派往城堡的名额也不是立刻把士兵派过去,是保留派过去的资格直到因为某种原因失去资格。在这里失去资格的原因是因为某个时刻下城堡数严格多于后面要用到的最少的士兵数,就选择最差的城堡淘汰掉。

posted @ 2019-12-16 12:19  KisekiPurin2019  阅读(294)  评论(0编辑  收藏  举报