Bzoj P1150 [CTSC2007]数据备份Backup___链表+贪心+线段树

题目大意:

给出同一个水平线上的 n n 个位置 Xi X i ,依次递增,
任意2个位置之间都可以配对,花费为距离差,每个位置最多仅能被配对一次,你要给 K K 对位置配对,问 K K 对位置的距离差之和最少是多少。

2n100000 2 ≤ n ≤ 100000
1kn/2 1 ≤ k ≤ n / 2
0Xi1000000000 0 ≤ X i ≤ 1000000000

分析:

显然,我们配对的 K K 对位置,
他们必定满足每一对都是相邻的 2 2 个位置,
否则必定会有更优的答案,
那么我们可以处理出所有相邻位置之间的距离差,
D1,D2,D3Dn2Dn1 D 1 , D 2 , D 3 … … D n − 2 , D n − 1
如果 K=1 K = 1 ,那么显然,答案为 min m i n { Dj D j }
如果 K2 K − 2 ,那么答案就有 2 2 种可能,
因为选了任意一个 Dj D j 以后, Dj1 D j − 1 , Dj+1 D j + 1 不能被选,
所以第一种可能就是选了最小值 Di D i ,以及除了 Di1,DiDi+1 D i − 1 , D i , D i + 1 以 外 的 最 小 值
第二种可能就是同时选了 Di1 D i − 1 Di+1 D i + 1
因为如果你不选了 Di D i 而且仅在了 Di1 D i − 1 或者 Di+1 D i + 1 中的某一个,那么你将那个换成 Di D i 肯定会更优。
那么我们就可以知道,最小值两边要么都不选,要么都选
所以我们可以这样做,
每次找到一个最小的 Dj D j 加入,
设这个最小值为 Di D i
然后删除 Di1,Di,Di+1 D i − 1 , D i , D i + 1 ,并且加入一个点权为 Di1+Di+1Di D i − 1 + D i + 1 − D i 的点,就是多一次反悔的机会,如果到时候我们发现选 Di+1 D i + 1 跟选 Di1 D i − 1 更优,那么我们就可以将那个位置跟这个位置同时给 Di1 D i − 1 , Di+1 D i + 1 ,同时将 Di D i 剔除。
然后考虑边界,
如果最小值在边上,那么我们可以发现,它必定是一定要被选到的,这个很容易理解,因为如果我们选了 2 2 个,
设为 Dx+Dy D x + D y ,那么就算其中一个因为与它相邻而不能被选,那么另外一个也必定能与它相加,那么我们从而使结果更小。
然后删除的过程可以利用链表实现,
而因为 Dj D j n1 n − 1 个,而 n n 105 10 5 ,那么我们就要利用一些数据结构去维护它的最小值,可是因为有删除操作,比较麻烦,所以我写的是线段树去维护。

代码:

#include
#include
#include
#include
#define N 100005

using namespace std;

typedef long long ll;

struct Node { ll num; int pre, nxt; }d[N];
int tree[5*N], n, k;

void Build(int x, int l, int r)
{
    if (l == r)
    {
        tree[x] = l;
        return;
    }
    int mid = (l + r) >> 1;
    Build(x * 2, l, mid);
    Build(x * 2 + 1, mid + 1, r);
    if (d[tree[x * 2]].num > d[tree[x * 2 + 1]].num) tree[x] = tree[x * 2 + 1];
        else tree[x] = tree[x * 2];
}

void Change(int x, int l, int r, int z)
{
    if (l == r) return;
    int mid = (l + r) >> 1;
    if (z <= mid) Change(x * 2, l, mid, z);
             else Change(x * 2 + 1, mid + 1, r, z);
    if (d[tree[x * 2]].num > d[tree[x * 2 + 1]].num) tree[x] = tree[x * 2 + 1];
        else tree[x] = tree[x * 2];
}

int main()
{
    scanf("%d %d", &n, &k);
    int x, y;
    ll Max_num = 0;
    scanf("%d", &y);
    for (int i = 1; i < n; i++)
    {
         scanf("%d", &x);
         d[i].num = x - y,
         d[i].pre = i - 1,
         d[i].nxt = i + 1,
         Max_num += d[i].num;
         y = x;
    }
    Max_num += 5;

    d[n].pre = n - 1;
    Build(1, 1, n - 1);
    ll ans = 0;
    for (int i = 1; i <= k; i++)
    {
         x = tree[1];
         ans += d[x].num;
         if (d[x].pre == 0 && d[x].nxt == n) break;
         int xpre = d[x].pre;
         int xnxt = d[x].nxt;
         if (xpre == 0)
         {
             d[d[xnxt].nxt].pre = 0;
             d[xnxt].num = Max_num;
             d[x].num = Max_num;
             Change(1, 1, n - 1, x);
             Change(1, 1, n - 1, xnxt);
         } else
         if (xnxt == n)
         {
             d[d[xpre].pre].nxt = n;
             d[xpre].num = Max_num;
             d[x].num = Max_num;
             Change(1, 1, n - 1, x);
             Change(1, 1, n - 1, xpre);
         } else
         {
             d[x].num = d[xpre].num + d[xnxt].num - d[x].num;
             d[d[xpre].pre].nxt = x;
             d[d[xnxt].nxt].pre = x;
             d[x].pre = d[xpre].pre;
             d[x].nxt = d[xnxt].nxt;
             d[xpre].num = Max_num;
             d[xnxt].num = Max_num;
             Change(1, 1, n - 1, x);
             Change(1, 1, n - 1, xpre);
             Change(1, 1, n - 1, xnxt);
         }
    }
    printf("%lld\n", ans);
    return 0;
}

你可能感兴趣的:(C++,贪心,线段树,链表)