BZOJ 1150 二叉堆(优先队列) + 贪心

本文介绍了一种利用贪心算法和网络流思想解决数轴上点的距离和最小化问题的方法。通过构建网络模型,实现反悔操作以优化选择过程,最终得到距离和的最小值。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

!!!
先打几个感叹号表示一下WA了一上午的愤慨。
总结一下题意,就是一条数轴上有n个点,要求选出k对点,求这k对点的距离和的最小值是多少。
首先可以证明我们不能嵌套着选,比如依次有1,2,3,4,四个点,选(1,2)(3,4)必然优于选(2,3)(1,4),所以就是选相邻的点,那么我们就把点与点之间的距离看成线段,或者一个值,题目就变成了从n-1个数中选出k个使得和最小,这样可以想到贪心,不过因为一个点最多只能在一条线段中(题目描述),所以这k个数必须两两不相邻,这样就不能直接贪心地排个序求前k个的和。
这里可以通过网络流的思想来想,求最大流可以退流(虽然很难知道具体的一条增广路是否退流),用到这里是及其的相似。
网络流里叫退流,这里就叫反悔吧,具体操作是:每当我们选取一个数,就把这个数以及它两边的数删除,然后再在它的位置上新加一个数,值为被选取的数两边数的和减去被选去的数,这样的话,选这个新加的数的话,就相当于反悔了,就相当于选取两边的数不选中间这个一开始被选取的数,不要担心如果最终答案是只选两边的数的其中的一个而不是上面所说的选取两边的数而不选中间的,因为还有其他反悔操作。这样的反悔操作,使得这个贪心的算法成立,仍然是每次取最小值,这里就可以用到堆,STL大法好,priority_queue便可解决我们的需求。
因为我们以上的需求,所以存入堆的是一个结构体,要保存很多东西:

struct data{
    int num, key, l, r, k;  
    bool operator < (const data& rhs) const {
        return key > rhs.key;
    }
}w[200005];

因为STL的p_q默认是大根堆,所以需要重定义一下操作符(或者以一种奇怪的方式申请一个小根堆)。num代表的是编号(在w里的编号,多用了一些空间申请了一个w数组是为了方便表示一个数左右的数),key就是权值,这个数的值了,l是这个数左边的数的编号,r是右边的数的编号,结构体里的这个k当然是与取k个数有关了,它记录的是这个结构体里代表着几个数,因为前面叙述的思路里,有合并的情况,删除3个数,新添一个数,那么选新添的这个数就是选了2了数,这是一个细节(一开始我就每选一个数,外面大循环k–,导致WA掉次数++)。
此外,因为我们会删除一些数,但是又无法直接从堆里删除,所以需要一个del数组代表这个数是否可用。因为结构体里申请了l,r所以我们不需要让相邻的数在w里的下标也相邻,所以新添加的数就从n开始继续往后排,这就是w数组开的范围比数据范围大的原因。这样以来del直接记录这个的w数组的位置就可以,不用担心会重复。剩下的操作就显得十分简单了,每次取出堆顶元素后累加到ans(小心int会爆哦),再新创建一个数,维护好左右关系即可(被选取的数的两边的数也会被删除,所以它左边的左边和右边的右边的数的结构体里的r值和l值也要变)。
这道题,我认为最关键的就是这个结构体,但是令我WA掉很多次的不是这个,而是边界问题,我原本将最左端的数的l和最右端的数的r都设为0,而w[0]内所有的数值都是0,这样一来就不会影响到任何的东西了,但就因为这个想法,WAWAWAWA根本停不下来。
需要将w[0].key设成无穷大,否则它有可能会不断的选取边界的数(因为如果w[0].key == 0, 这么小的数,很容易被选到吧)。
这不只是一个细节,叫它粗节好了。。这是在构建思路时产生的漏洞,这种错误是比错过一个细节更为致命的,这就要求更多地独立去思考,独自去构建完整的思路,兼以思维的缜密性。
下面是完整代码:

#include <cstdio>
#include <queue>
using namespace std;

int n, k; 
unsigned long long ans;
bool del[200005];

struct data{
    int num, key, l, r, k;  
    bool operator < (const data& rhs) const {
        return key > rhs.key;
    }
}w[200005]; 
int cnt;
priority_queue <data> d;

int main()
{   
    scanf("%d %d", &n, &k);
    for(int i = 1, t1, t2; i <= n; i++, t2 = t1){
        scanf("%d", &t1);
        if(i > 1){
            w[i-1].num = i-1;
            w[i-1].key = t1 - t2;
            if(i != 1) w[i-1].l = i-2;
            if(i != n) w[i-1].r = i;
            w[i-1].k = 1;
            d.push(w[i-1]);
        }   
    }   cnt = n-1;
    w[0].l = w[0].r = w[0].k = 0;
    w[0].key = (1<<30);
    while(k > 0){
        while(1){   
            data t = d.top();
            d.pop();
            int num = t.num, l = w[num].l, r = w[num].r;
            if(del[num]) continue;
            del[num] = del[l] = del[r] = 1;
            ans += w[num].key;
            k -= w[num].k;
            cnt++;
            w[cnt].num = cnt;
            w[cnt].key = w[l].key + w[r].key - w[num].key;
            w[cnt].l = w[l].l;
            w[cnt].r = w[r].r;
            w[cnt].k = w[l].k + w[r].k - w[num].k;
            w[w[l].l].r = cnt;
            w[w[r].r].l = cnt; 
            d.push(w[cnt]);
            break;
        }
    }
    printf("%lld", ans);
    return 0;
} 
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值