【BZOJ1150】数据备份(堆/优先队列)
- 题目:
- 思路:
先获取每两个相邻办公楼之间的距离。 Di(1≤i≤n-1)-
当k=1时,选择最小的 Di
-
当k=2时
- 方案一:选择最小的 Di和除了 Di−1,Di,Di+1之外的其他数中的最小值
- 方案二:选择最小的 Di旁边的 Di−1,Di+1
由于不知道每次是选择第一种方案还是第二种,所以每次先将最小的 Di累加入答案,再将 Di−1,Di,Di+1归为 Di−1+Di+1−Di,如果下次选择 Di−1+Di+1−Di的话,那么就是属于第二种情况,否则就是第一种。
-
实现:
(1)链表存 D1,...,Dn−1并记录每个 Di在小根堆的编号,同样小根堆里也要记录每个编号上的 Di在链表中的位置。因为每次要处理的是当前的最小值以及最小值的前后两个数据。注意,这种方法需要特别处理最小值 Di没有前驱或者后继的情况,按照上述思路这种情况只能是第一种方案。
(2)优先队列实现。因为每次的 Di−1,Di,Di+1要归为 Di−1+Di+1−Di,相当于 Di−1,Di+1被删除, Di用 Di−1+Di+1−Di代替。可以用数组标记下哪些下标对应的 D值被删除了,今后便不再处理中这些位置上的数据。为了避免用链表实现是的特殊情况,可以将 Dn−1的后继定为 D0,且 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;
}