P3620 [APIO/CTSC 2007] 数据备份

P3620 [APIO/CTSC 2007] 数据备份


题目描述

你在一家 IT 公司为大型写字楼或办公楼(offices)的计算机数据做备份。然而数据备份的工作是枯燥乏味的,因此你想设计一个系统让不同的办公楼彼此之间互相备份,而你则坐在家中尽享计算机游戏的乐趣。

已知办公楼都位于同一条街上。你决定给这些办公楼配对(两个一组)。每一对办公楼可以通过在这两个建筑物之间铺设网络电缆使得它们可以互相备份。

然而,网络电缆的费用很高。当地电信公司仅能为你提供 K 条网络电缆,这意味着你仅能为 K 对办公楼(或总计 2K 个办公楼)安排备份。任一个办公楼都属于唯一的配对组(换句话说,这 2K 个办公楼一定是相异的)。

此外,电信公司需按网络电缆的长度(公里数)收费。因而,你需要选择这 K对办公楼使得电缆的总长度尽可能短。换句话说,你需要选择这 K 对办公楼,使得每一对办公楼之间的距离之和(总距离)尽可能小。

下面给出一个示例,假定你有 5 个客户,其办公楼都在一条街上,如下图所示。这 5 个办公楼分别位于距离大街起点 1km, 3km, 4km, 6km 和 12km 处。电信公司仅为你提供 K=2 条电缆。

上例中最好的配对方案是将第 1 个和第 2 个办公楼相连,第 3 个和第 4 个办公楼相连。这样可按要求使用 K=2 条电缆。第 1 条电缆的长度是 \(3\text{km} - 2\text{km} = 1\text{km}\),第 2 条电缆的长度是 6km―4km = 2 km。这种配对方案需要总长 4km 的网络电缆,满足距离之和最小的要求。


题解

显而易见,相邻两个办公楼之间连接电缆跨度最小,最为高效。但是由于每个办公楼只能属于唯一配对,所以会有以下的考虑:

  1. 朴素贪心:考虑每次选取最小的,在此选择最小的,直到结束,但是由于唯一配对的限制,显而易见这样的贪心是错误的。
  2. \(dp\):\(O(n^3)\)

    \(f[i,t]\)表示在第\(i\)个办公楼,使用\(t\)次电缆的情况,最终的答案应为:

    \(\text{ans} = \min_{i ≥2k} \{f[i,k]\}\)

\[
f[i,t] = \min_{2 ≤ j ≤ i - 2}\{f[j][t-1] + x[i] - x[i - 1]\}
\]

  1. \(dp\):\(O(n^2)\)

    \(f[i,t]\)表示在第\(i\)个办公楼,使用\(t\)次电缆的情况,最终的答案应为:

    \(\text{ans} = \min_{i ≥2k} \{f[i,k]\}\)

\[
f[i][t] = \min
\begin{cases}
f[i-1][t] ~~~~~~&\text{not use}\f[i-2][t-1] + x[i] - x[i - 1]&\text{use}
\end {cases}
\]

  1. 高级贪心(反悔,赎回)

    从一个简单的例子看起,首先对于下列数列(表示题中的差分数列):

    \(7,~5,~3,~1,~3,~5,~7\)

    找出最小:\(1\),删去与其相邻的两个以及自身,改为\(3 + 3 - 1 = 5\):

    \(7,~5,~5,~5,~7\)

    周而往复,可以发现上述的操作即为正解。

    对于任意差分数列:
    \[
    d_1,~d_2,~d_3,~\cdots,~d_n
    \]
    而言,任意的\(d_i, i\in(1,n)\),若将其选出,删去原数列中的\(d_{i-1},~d_{i+1}\),并且将\(d_i\)更改为:\(d_{i + 1} + d_{i - 1} - d_i\)。接下来,可以验证这样的操作的反悔正确性:

    若原差分数列的最小值:\(d_{\min} = d_i = \min\{d_j, j \in [1, n]\}, i \in (1, n)\)。进过上述的操作之后,若此时的最小值为\(d_{\min}’ = d_i’ = \min\{d_j’, j \in[1, n - 2]\}, i \in (1, n - 2)\),则两次取出的最小值之和:\(d_\min + d_\min’ = d_{i - 1} + d_{i + 1} - d_i + d_i = d_{i - 1} + d_{i + 1}\),所以这两个数仍然为原差分数列当中不重复的两个元素

    但是这种验证无法证明其边界的正确性,即当\(d_\min = d_1, d_\min = d_n\)的情况。

    不妨令原来的数列为:
    \[
    d_0 = \infty,~d_1,~d_2,~d_3,~\cdots,~d_n, d_{n + 1} = \infty
    \]
    若此时最小的为\(d_\min = d_1\),则将\(d_1\)改为\(d_1’ = \infty + d_2 - d_1 = \infty\),所以此时任然可以维护最优,因为数列已经变为:
    \[
    d_1' = \infty,~d_3,~d_4,~d_5,~\cdots,~d_n,~d_{n+1} = \infty
    \]
    而对于队尾的\(d_n\)同理。

    所以希望上述的操作可以再常数,或者对数的事件复杂度内完成。

    所以需要用到以下三个关键的操作:

    (一)查询、维护前继和后继

    (二)懒删除

    (三)间接比较:存序号

    所以整个过程当中,需要维护一个双向链表,和一个堆,主程序思路:

    1. 构建差分队列
    2. 构建双向链表
    3. 构建堆(其中存值)
    4. while (k --> 0)的情况之下,按照上述操作维护双向链表

    代码:

    #include <iostream>
    #include <algorithm>
    
    using namespace std;
    typedef long long ll;
    const int maxn = 100005;
    ll n, k, ans, val[maxn], prv[maxn], nxt[maxn];
    
    struct Heap {
        ll size;
        ll Q[maxn];
        bool dead[maxn];
    
        void del_heap(int a) {
            dead[a] = true;
        }
    
        ll top() {
            if (!empty()) {
                ll a = Q[1];
                while (dead[a]) {
                    pop();
                    a = (empty() ? 0 : Q[1]);
                }
                return a;
            } else return 0;
        }
    
        bool empty() {
            return !size;
        }
    
        void swap_value(ll index_a, ll index_b) {
            ll tmp = Q[index_a];
            Q[index_a] = Q[index_b];
            Q[index_b] = tmp;
        }
    
        void pop() {
            swap_value(1, size);
            size --;
            down(1);
        }
    
        void down(ll i) {
            ll j = 2 * i;
            if (j + 1 <= size && val[Q[j + 1]] < val[Q[j]])
                j = j + 1;
            if (j <= size && val[Q[j]] < val[Q[i]]) {
                swap_value(i, j);
                down(j);
            }
        }
    
        void push(ll x) {
            size ++;
            ll i = size;
            while (i >= 2) {
                ll father = i / 2;
                if (val[Q[father]] <= val[x]) break;
                Q[i] = Q[father];
                i = father;
            }
            Q[i] = x;
        }
    
    } heap;
    
    void del_link(ll i) {
        ll prev_pointer = prv[i], next_pointer = nxt[i];
        prv[next_pointer] = prev_pointer;
        nxt[prev_pointer] = next_pointer;
        // Also Right
        // prv[nxt[i]] = prv[i];
        // nxt[prv[i]] = nxt[i];
    }
    
    int main() {
        cin >> n >> k;
        // inital data
        ll tmp1 = 0, tmp2 = 0;
        cin >> tmp1;
        for (int i = 1; i <= n - 1; i ++) {
            cin >> tmp2;
            val[i] = tmp2 - tmp1;
            tmp1 = tmp2;
        }
        // create list
        for (int i = 1; i <= n - 1; i ++) {
            nxt[i] = i + 1;
            prv[i] = i - 1;
        }
        prv[1] = 0; nxt[n - 1] = 0;
        val[0] = 1000000000000;
    
        // create heap
        for (int i = 1; i <= n - 1; i ++) heap.push(i);
    
        while (k --> 0) {
            ll id = heap.top();
            ans += val[id];
            ll left_pointer = prv[id]; ll right_pointer = nxt[id];
            heap.del_heap(left_pointer);
            heap.del_heap(right_pointer);
            del_link(left_pointer);
            del_link(right_pointer);
            val[id] = val[left_pointer] + val[right_pointer] - val[id];
            heap.down(1);
        }
        cout << ans << endl;
        return 0;
    }

    普通建堆,时间复杂度:
    \[
    \begin {align}
    &\log 1 + \log 2 + \log 3 + \log 4 + \cdots + \log n \=& \int_0^n \log x\mathrm dx < n\log n
    \end {align}
    \]
    但是有\(O(n)\)的建堆算法:

    // create heap
    
    // O(nlog n)
    // for (int i = 1; i <= n - 1; i ++) heap.push(i);
    
    // O(n)
    for (int i = 1; i <= n - 1; i ++) { heap.Q[i] = i; }
    heap.size = n - 1;
    for (int i = heap.size / 2; i > 0; i --) { heap.down(i); }

原文地址:https://www.cnblogs.com/jeffersonqin/p/11222402.html

时间: 2024-08-30 18:19:11

P3620 [APIO/CTSC 2007] 数据备份的相关文章

洛谷P3620 [APIO/CTSC 2007] 数据备份 [堆,贪心,差分]

题目传送门 题目描述 你在一家 IT 公司为大型写字楼或办公楼(offices)的计算机数据做备份.然而数据备份的工作是枯燥乏味的,因此你想设计一个系统让不同的办公楼彼此之间互相备份,而你则坐在家中尽享计算机游戏的乐趣. 已知办公楼都位于同一条街上.你决定给这些办公楼配对(两个一组).每一对办公楼可以通过在这两个建筑物之间铺设网络电缆使得它们可以互相备份. 然而,网络电缆的费用很高.当地电信公司仅能为你提供 K 条网络电缆,这意味着你仅能为 K 对办公楼(或总计 2K 个办公楼)安排备份.任一个

解题:APIO/CTSC 2007 数据备份

题面 用双向链表把相邻两项的差串起来,用大根堆维护价值,每次贪心取最大的$x$.取完之后打标记删掉$pre[x]$和$nxt[x]$,之后用$val[pre[x]]+val[nxt[x]]-val[x]$替换这个$x$塞进堆里去,注意边界要连上一个极值 1 #include<queue> 2 #include<cstdio> 3 #include<cstring> 4 #include<algorithm> 5 using namespace std; 6

[APIO/CTSC 2007]数据备份

嘟嘟嘟 这竟然是一道贪心题,然而我在不看题解之前一直以为是dp. 首先最优的配对一定是相邻两个建筑物配对,所以我们求出差分数组,就变成了在n - 1个数中选出不相邻的k个数,使这k个数的和最小. 贪心是在回事呢?首先把所有点放在一个小根堆中,然后如果取出一个点ai,就把ai-1 + ai+1 - ai放到小根堆中,这样如果以后选了ai-1 + ai+1 - ai这个数,就把前面选的ai抵消了,所以这两次操作就相当于选了ai-1和ai+1这两个数. 每选一次就合并了两个数,那么进行k次就选了k个数

bzoj1150 [CTSC2007]数据备份

Description 你在一家 IT 公司为大型写字楼或办公楼(offices)的计算机数据做备份.然而数据备份的工作是枯燥乏味的,因此你想设计一个系统让不同的办公楼彼此之间互相备份,而你则坐在家中尽享计算机游戏的乐趣.已知办公楼都位于同一条街上.你决定给这些办公楼配对(两个一组).每一对办公楼可以通过在这两个建筑物之间铺设网络电缆使得它们可以互相备份.然而,网络电缆的费用很高.当地电信公司仅能为你提供 K 条网络电缆,这意味着你仅能为 K 对办公楼(或总计2K个办公楼)安排备份.任一个办公楼

数据备份方案

经常有朋友发生了数据丢失时找我帮忙,我发现数据备份是最科学的解决方案.于是花时间把我这几年积累的数据备份方案整理出来,希望能帮到大家. 先看看几个典型情景: 我经常用手机拍照,万一我手机丢了,里面的照片的价值比一台新手机还大. 我把我的许多资料存在移动硬盘里了,结果今天硬盘出问题,读不出来,有什么办法可以挽回? 我的许多重要数据都存在我的个人电脑里,结果昨晚电脑被贼偷了. 我刚误修改了一份Excel文件,而且还保存了,我想要回修改前的文件. 以上是常见的几个代表情景,如果真的发生了,往往很难处理

Bzoj1150 数据备份Backup

Description 你在一家 IT 公司为大型写字楼或办公楼(offices)的计算机数据做备份.然而数据备份的工作是枯燥乏味 的,因此你想设计一个系统让不同的办公楼彼此之间互相备份,而你则坐在家中尽享计算机游戏的乐趣.已知办公 楼都位于同一条街上.你决定给这些办公楼配对(两个一组).每一对办公楼可以通过在这两个建筑物之间铺设网 络电缆使得它们可以互相备份.然而,网络电缆的费用很高.当地电信公司仅能为你提供 K 条网络电缆,这意味 着你仅能为 K 对办公楼(或总计2K个办公楼)安排备份.任一

rsync远程数据备份配置之再次总结

一.实验环境 主机名  网卡ip  默认网关 用途 nfs-server 10.0.0.11 10.0.0.254 rsync服务器端 web-client01 10.0.0.12 10.0.0.254 rsync客服端 web-client02 10.0.0.13 10.0.0.254 rsync客服端 二.实验步骤 1.什么是rsync?rsync是一款开源的,快速的,多功能的可实现全量及增量的数据备份同步的优秀工具,适用于多种操作系统之上.2.rsync特性1)支持拷贝链接文件特殊文件2)

mysql定时数据备份工具(c#)

此博文的出处 为 http://blog.csdn.net/zhujunxxxxx/article/details/40124773如果进行转载请注明出处.本文作者原创,邮箱[email protected],如有问题请联系作者 为了确保数据的安全,我们往往要对数据进行备份.但是为了减少我们的工作量,我写了一个简单的数据备份工具,实现定时备份数据库. 其实程序很简单,数据备份的工作就是几个mysql的命令而已. 先看看程序的运行界面 可以看到界面是十分的简单的 我们使用的是命令行来进行数据备份,

elasticsearch数据备份恢复

本文主要介绍elasticsearch集群数据备份及恢复,利用共享文件系统,通过快照方式备份. 集群的部署参考:http://hnr520.blog.51cto.com/4484939/1876467 一.配置修改 1.配置文件必须添加如下参数 path.repo:  /mnt/backups/es_mybak 二.数据备份 1.创建备份仓库 curl -XPUT 'http://192.168.115.11:9200/_snapshot/EsBackup' -d '{   "type"