【BZOJ1150】數據備份(堆/優先隊列)

傳送門

  • 題目:在這裏插入圖片描述
  • 思路:
    先獲取每兩個相鄰辦公樓之間的距離。DiD_i(1≤i≤n-1)
    • 當k=1時,選擇最小的DiD_i

    • 當k=2時

      • 方案一:選擇最小的DiD_i和除了Di1,Di,Di+1D_{i-1},D_i,D_{i+1}之外的其他數中的最小值
      • 方案二:選擇最小的DiD_i旁邊的Di1,Di+1D_{i-1},D_{i+1}

      由於不知道每次是選擇第一種方案還是第二種,所以每次先將最小的DiD_i累加入答案,再將Di1,Di,Di+1D_{i-1}, D_i, D_{i+1}歸爲Di1+Di+1DiD_{i-1}+D_{i+1}-D_i,如果下次選擇Di1+Di+1DiD_{i-1}+D_{i+1}-D_i的話,那麼就是屬於第二種情況,否則就是第一種。

實現:
(1)鏈表存D1,...,Dn1D_1,...,D_{n-1}並記錄每個DiD_i在小根堆的編號,同樣小根堆裏也要記錄每個編號上的DiD_i在鏈表中的位置。因爲每次要處理的是當前的最小值以及最小值的前後兩個數據。注意,這種方法需要特別處理最小值DiD_i沒有前驅或者後繼的情況,按照上述思路這種情況只能是第一種方案。
(2)優先隊列實現。因爲每次的Di1,Di,Di+1D_{i-1}, D_i, D_{i+1}要歸爲Di1+Di+1DiD_{i-1}+D_{i+1}-D_i,相當於Di1,Di+1D_{i-1}, D{i+1}被刪除,DiD_iDi1+Di+1DiD_{i-1}+D_{i+1}-D_i代替。可以用數組標記下哪些下標對應的DD值被刪除了,今後便不再處理中這些位置上的數據。爲了避免用鏈表實現是的特殊情況,可以將Dn1D_{n-1}的後繼定爲D0D_0,且D0D_0無窮大。

  • 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;
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章