传送门

  • 题目:
  • 思路:
    先获取每两个相邻办公楼之间的距离。 D i D_i Di(1≤i≤n-1)
    • 当k=1时,选择最小的 D i D_i Di

    • 当k=2时

      • 方案一:选择最小的 D i D_i Di和除了 D i 1 , D i , D i + 1 D_{i-1},D_i,D_{i+1} Di1,Di,Di+1之外的其他数中的最小值
      • 方案二:选择最小的 D i D_i Di旁边的 D i 1 , D i + 1 D_{i-1},D_{i+1} Di1,Di+1

      由于不知道每次是选择第一种方案还是第二种,所以每次先将最小的 D i D_i Di累加入答案,再将 D i 1 , D i , D i + 1 D_{i-1}, D_i, D_{i+1} Di1,Di,Di+1归为 D i 1 + D i + 1 D i D_{i-1}+D_{i+1}-D_i Di1+Di+1Di,如果下次选择 D i 1 + D i + 1 D i D_{i-1}+D_{i+1}-D_i Di1+Di+1Di的话,那么就是属于第二种情况,否则就是第一种。

实现:
(1)链表存 D 1 , . . . , D n 1 D_1,...,D_{n-1} D1,...,Dn1并记录每个 D i D_i Di在小根堆的编号,同样小根堆里也要记录每个编号上的 D i D_i Di在链表中的位置。因为每次要处理的是当前的最小值以及最小值的前后两个数据。注意,这种方法需要特别处理最小值 D i D_i Di没有前驱或者后继的情况,按照上述思路这种情况只能是第一种方案。
(2)优先队列实现。因为每次的 D i 1 , D i , D i + 1 D_{i-1}, D_i, D_{i+1} Di1,Di,Di+1要归为 D i 1 + D i + 1 D i D_{i-1}+D_{i+1}-D_i Di1+Di+1Di,相当于 D i 1 , D i + 1 D_{i-1}, D{i+1} Di1,Di+1被删除, D i D_i Di D i 1 + D i + 1 D i D_{i-1}+D_{i+1}-D_i Di1+Di+1Di代替。可以用数组标记下哪些下标对应的 D D D值被删除了,今后便不再处理中这些位置上的数据。为了避免用链表实现是的特殊情况,可以将 D n 1 D_{n-1} Dn1的后继定为 D 0 D_0 D0,且 D 0 D_0 D0无穷大。

  • ac代码:
    (1)链表和小根堆实现:
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int maxn = 1e5+10;
struct LinkNode{
    ll val;
    int pre, nxt, hid;//在堆中对应的id
}link[maxn];
struct HeapNode{
    ll val;
    int lid;//在链表中对应的id
}heap[maxn];
int n, k, tot;
ll ans;
 
void up(int p)
{
    while(p>1)
    {
        if(heap[p].val<heap[p/2].val)
        {
            swap(heap[p], heap[p/2]);
            swap(link[heap[p].lid].hid, link[heap[p/2].lid].hid);
            p/=2;
        }
        else break;
    }
}
void down(int p)
{
    int pp = p*2;
    while(pp<=tot)
    {
        if(pp<tot && heap[pp].val>heap[pp+1].val) pp++;
        if(heap[p].val>heap[pp].val)
        {
            swap(heap[p], heap[pp]);
            swap(link[heap[p].lid].hid, link[heap[pp].lid].hid);
            p = pp; pp = p*2;
        }
        else break;
    }
}
void del_link(int p)
{
    link[link[p].pre].nxt = link[p].nxt;
    link[link[p].nxt].pre = link[p].pre;
}
void del_heap(int p)
{
    swap(link[heap[p].lid].hid, link[heap[tot].lid].hid);
    heap[p] = heap[tot--];
    up(p); down(p);
}
int main()
{
    //freopen("/Users/zhangkanqi/Desktop/11.txt","r",stdin);
    int pre = 0, now = 0, nxt = 0;
    ll v;
    scanf("%d %d %d", &n, &k, &pre); tot = n-1;
    for(int i = 1; i < n; i++)
    {
        scanf("%d", &now); v = now-pre;
        link[i] = {v, i-1, i+1, i};
        heap[i] = {v, i}; up(i);
        pre = now;
    }
    for(int i = 1; i <= k; i++)
    {
        ans += heap[1].val;
        now = heap[1].lid; pre = link[now].pre; nxt = link[now].nxt;//确定堆顶在链表中的的pre,now,nxt
        if(pre==0 || nxt==n)
        {
            if(pre==0) del_link(nxt), del_heap(link[nxt].hid);
            if(nxt==n) del_link(pre), del_heap(link[pre].hid);
            del_link(now), del_heap(1);
        }
        else
        {
            v = link[pre].val+link[nxt].val-link[now].val;
            heap[1].val = link[now].val = v; down(1);//更新堆顶并down
            del_link(pre), del_heap(link[pre].hid);
            del_link(nxt), del_heap(link[nxt].hid);
        }
    }
    printf("%lld\n", ans);
    return 0;
}

(2)优先队列和数组标记实现:

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int maxn = 1e5+10;
int n, k;
ll ans;
bool del[maxn];
int pre[maxn], nxt[maxn];
ll v[maxn];
struct node{
    int id;
    ll val;
    friend bool operator <(node a, node b)
    {
        return a.val>b.val;
    }
};
priority_queue<node> q;
int main()
{
    //freopen("/Users/zhangkanqi/Desktop/11.txt","r",stdin);
    ll last, now;
    scanf("%d %d %lld", &n, &k, &last);
    for(int i = 1; i < n ; i++)
    {
        scanf("%lld", &now);
        v[i] = now-last;
        q.push({i, v[i]});
        pre[i] = i-1; nxt[i] = i+1==n?0:i+1;//构成循环,不需要再考虑特殊的前驱和后继
        last = now;
    }
    v[0] = INT_MAX;
    while(k)
    {
        node x= q.top(); q.pop();
        int id = x.id;
        if(del[id]) continue;
        ans += v[id];
        del[pre[id]] = true; del[nxt[id]] = true;
        v[id] = v[pre[id]]+v[nxt[id]]-v[id]; q.push({id, v[id]});
        pre[id] = pre[pre[id]], nxt[id] = nxt[nxt[id]];
        nxt[pre[id]] = id, pre[nxt[id]] = id;//pre[x],nxt[x]已经由上一行改变
        k--;
    }
    printf("%lld\n", ans);
    return 0;
}

此外还学到了一种重载运算符的方法,第一次见:

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int maxn = 1e5+10;
int n, k;
ll ans;
bool del[maxn];
int pre[maxn], nxt[maxn];
ll v[maxn];
struct cmp{
    bool operator()(int a, int b)
    {
        return v[a]>v[b];
    }
};
priority_queue<int, vector<int>, cmp> q;
int main()
{
    //freopen("/Users/zhangkanqi/Desktop/11.txt","r",stdin);
    ll last, now;
    scanf("%d %d %lld", &n, &k, &last);
    for(int i = 1; i < n ; i++)
    {
        scanf("%lld", &now);
        v[i] = now-last;
        q.push(i);
        pre[i] = i-1; nxt[i] = i+1==n?0:i+1;//构成循环,不需要再考虑特殊的前驱和后继
        last = now;
    }
    v[0] = INT_MAX;
    while(k)
    {
        int x = q.top(); q.pop();
        if(del[x]) continue;
        ans += v[x];
        del[pre[x]] = true; del[nxt[x]] = true;
        v[x] = v[pre[x]]+v[nxt[x]]-v[x]; q.push(x);
        pre[x] = pre[pre[x]], nxt[x] = nxt[nxt[x]];
        nxt[pre[x]] = x, pre[nxt[x]] = x;//pre[x],nxt[x]已经由上一行改变
        k--;
    }
    printf("%lld\n", ans);
    return 0;
}