数据备份
      
- 题目链接
- 数据结构/优先队列/链表
- STL/list
- 贪心
          Description
      
有 N 座办公楼位于同一条街上,已知每座办公楼距离大街起点的距离。现要用 K 条网络电缆将办公楼连接,且任意一个办公楼都属于唯一配对组(即连接的 2K 个办公楼一定是相异的)。求 K 条网络电缆最小的长度总和。
        
          Solution
      
容易发现,最优解中每两个配对的办公楼一定是相邻的。于是先求出两个相邻办公楼之间的距离,记为 D1,D2,D3,...,DN−1。于是问题可以转化为:从数列 D 中选取不超过 K 个不相邻的数,使它们的和最小。
考虑贪心:
- 如果 K=1,答案显然是数列 D 中的最小值
- 如果 K=2,可以证明答案是以下两种情况之一:
- 选取 Di,以及除了 Di−1,Di,Di+1 之外其他数中的最小值
- 选取最小值 Di左右两侧的两个数,即 Di−1 和 Di+1
- 即最优解中,最小值左右两侧的数要么选,要么都不选
 
- 为防止选取局部最优解,可以先选取数列 D 中的最小值 Di,然后把 Di−1,Di,Di+1 删除,把 Di−1+Di+1−Di 插入到数列 D 中之前执行删除的位置。这样如果之后选了 Di−1+Di+1−Di,相当于没有选 Di,换上了 Di−1 和 Di+1。
于是可建立一个初始有 N−1 个节点的链表,链表节点记录数值 Di,节点编号 Idi,以及链表节点指针或对应的迭代器。再建立一个优先队列(小根堆),将链表节点存入。K 次操作中,取出堆顶的链表节点 ListNode,在链表中删除 ListNode−>prev 和 ListNode−>next,并创建新节点(数值为 ListNode−>prev−>data+ListNode−>next−>data−ListNode−>data)插入链表和优先队列。
注意:为防止越界或简化边界情况判断,可以在链表起始和末尾插入极大值。
        
          Code
      
        
          指针版双向链表
      
指针版双向链表| 12
 3
 4
 5
 6
 7
 8
 9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 
 | #include <bits/stdc++.h>
 using namespace std;
 using LL = long long;
 
 constexpr int MAXN = 1e5 + 5;
 constexpr LL INF = 1e18;
 
 LL n, k, dis[MAXN], ans, cnt;
 
 struct ListNode {
 ListNode() = default;
 bool operator<(const ListNode &other) const {
 return this->data == other.data ? this->id > other.id : this->data > other.data;
 }
 ListNode *prev = NULL, *next = NULL, *cur = NULL;
 LL data = 0, id = -1;
 };
 map<int, bool> deleted;
 
 struct List {
 List() {
 this->head = new ListNode(), this->tail = new ListNode();
 this->head->next = this->tail;
 this->tail->prev = this->head;
 }
 
 ListNode *insert(LL data) {
 ListNode *cur = new ListNode();
 cur->data = data, cur->id = cnt++, cur->cur = cur;
 tail->prev->next = cur, cur->prev = tail->prev;
 cur->next = tail, tail->prev = cur;
 return cur;
 }
 
 ListNode *erase(ListNode *cur) {
 LL data = cur->prev->data + cur->next->data - cur->data;
 ListNode *newListNode = new ListNode();
 newListNode->cur = newListNode;
 newListNode->data = data, newListNode->id = cnt++;
 
 ListNode *prev = cur->prev, *next = cur->next;
 prev->prev->next = newListNode, newListNode->prev = prev->prev;
 next->next->prev = newListNode, newListNode->next = next->next;
 
 deleted[prev->id] = deleted[next->id] = deleted[cur->id] = true;
 return newListNode;
 }
 
 ~List() {}
 ListNode *head = NULL, *tail = NULL;
 } lst;
 
 priority_queue<ListNode> q;
 inline void calcLen(int tot = 0) {
 while (!q.empty() && tot < k) {
 ListNode* cur = q.top().cur;
 q.pop();
 if (deleted[cur->id]) continue;
 ans += cur->data, tot++;
 ListNode *newListNode = lst.erase(cur);
 q.push(*newListNode);
 }
 }
 
 int main() {
 scanf("%lld %lld", &n, &k);
 for (int i = 0; i < n; ++i) scanf("%lld", dis + i);
 lst.insert(INF);
 for (int i = 0; i < n - 1; ++i) q.push(*lst.insert(dis[i + 1] - dis[i]));
 lst.insert(INF);
 calcLen();
 printf("%lld\n", ans);
 return 0;
 }
 
 | 
 
        
          STL list
      
STL 的 list 为 环形双向链表, 本题中用到以下功能。
- 通过 iterator−− 或 iterator++ 获得 prev 或 next 的迭代器。
- insert() 成员函数在指定位置处插入值,并返回插入的元素所对应的的迭代器
- erase() 成员函数对指定位置进行删除,返回删除的元素后一个位置的迭代器
STL list| 12
 3
 4
 5
 6
 7
 8
 9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 
 | #include <bits/stdc++.h>
 using namespace std;
 using LL = long long;
 
 constexpr int MAXN = 1e5 + 5;
 constexpr LL INF = 1e15;
 
 LL n, k, dis[MAXN], ans, cnt;
 
 struct ListNode {
 ListNode() = default;
 ListNode(LL _data, LL _id) : data(_data), id(_id) {}
 bool operator<(const ListNode &other) const {
 return this->data == other.data ? this->id > other.id : this->data > other.data;
 }
 LL data = INF, id = -1;
 list<ListNode>::iterator itr;
 };
 list<ListNode> lst;
 priority_queue<ListNode> q;
 map<LL, bool> deleted;
 
 inline void calcLen(int tot = 0) {
 while (!q.empty() && tot < k) {
 auto cur = q.top();
 q.pop();
 if (deleted[cur.id]) continue;
 ans += cur.data, tot++;
 
 auto itr = cur.itr;
 --itr;
 LL data = itr->data; deleted[itr->id] = true, itr = lst.erase(itr);
 data -= itr->data, deleted[itr->id] = true, itr = lst.erase(itr);
 data += itr->data, deleted[itr->id] = true, itr = lst.erase(itr);
 
 auto newNode = ListNode(data, cnt++);
 newNode.itr = lst.insert(itr, newNode);
 q.push(newNode);
 }
 }
 
 int main() {
 std::ios::sync_with_stdio(false), cin.tie(0), cout.tie(0);
 cin >> n >> k;
 for (int i = 0; i < n; ++i) cin >> dis[i];
 lst.emplace_back(ListNode(INF, cnt++));
 for (int i = 0; i < n - 1; ++i) {
 auto newNode = ListNode(dis[i + 1] - dis[i], cnt++);
 newNode.itr = lst.insert(lst.end(), newNode);
 q.push(newNode);
 }
 lst.emplace_back(ListNode(INF, cnt++));
 calcLen();
 cout << ans << endl;
 return 0;
 }
 
 | 
 
        
          类似题目
      
种树
国家集训队 种树
BACKUP Backup Files