bzoj1150: [CTSC2007]数据备份Backup 贪心

70 篇文章 0 订阅
17 篇文章 0 订阅

这是一道很好的题目,正常人都想不出做法。

我还记得题解是说:

(1)想到动规,但是T到死。。。

(2)转化成网络流,还是T的不行

(3)咦,好像是贪心欸,做出来了(你在卖萌!)

 

其实算法很简单,首先我们知道必须找相邻的两个进行配对,但是不是直接找最小,而是每次要找最短的一段(后面会解释什么叫"段"),于是可以用堆来维护。

具体做法是找出当前最短的段X,设左边那段叫L,右边那段叫R,那么:

只要(1)ans += dis[X]  (2)删除X、L、R三段,即堆中删除dis[X], dis[L], dis[R] (3)堆中加入dis[L] + dis[R] - dis[X]即可

删除我用的是时间标记法,比较容易实现。

 

什么叫段:就是i到j之间的全部都两两配对形成最小值的区间的啊。。

一开始有n - 1段,每次操作会造成减少两段,这是合理的,下面会解释。

 

算法合理性解释:

如果你取了一段X,但是其实取左右L和R比现在更优,那么下一次你会取L + R - X这一"段", 最后的ans = dis[L] + dis[R],也就是取了L和R。

这就解释了为什么会操作一次减少两段,因为后面如果选了dis[L] + dis[R] - dis[X]这段等于一下子选了两段,所以前面就会先减掉1段。 

#include <cstdlib>
#include <cstdio>
#include <queue>
#include <utility>
#include <cstring>
#include <vector>
const int arr = 200000;
using namespace std;
typedef pair<int, pair <int, int> > PP;
priority_queue <PP> heap;
int n, K, ans, L, R, pos, l[arr], r[arr], t[arr], a[arr];
bool v[arr];
int main()
{
    int maxn = 2000000000;
    scanf("%d%d", &n, &K);
    for (int i = 1; i <= n; ++i)
        scanf("%d", a + i);
    for (int i = n; i > 1; --i)
        a[i] -= a[i - 1];

    a[1] = a[++n] = maxn;

    while (!heap.empty()) heap.pop();
    for (int i = 1; i <= n; ++i)
    {
        heap.push(make_pair(-a[i], make_pair(i, 1)));
        l[i] = i - 1, r[i] = i + 1, t[i] = 1;
    }
    memset(v, 0, sizeof(v));
    PP x;
    ans = 0;
    while(K--)
    {
        for(x=heap.top();v[x.second.first]||t[x.second.first]!=x.second.second;heap.pop(),x=heap.top());
        x=heap.top();
        ans-=x.first;
        int pos=x.second.first;
        int L=l[pos],R=r[pos];
        r[L]=r[R];
        l[r[R]]=L;
        v[pos]=v[R]=1;
        a[L]+=a[R]-a[pos];
        heap.push(make_pair(-a[L], make_pair(L, ++t[L])));
    }
    printf("%d\n", ans);
    return 0;
}

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值