51Nod-1461-稳定桌

ACM模版

描述

描述

题解

这个是 CF 上的原题改的,将 di 的数据放大了,所以不能普通的枚举代价了,而需要用数据结构优化处理,这里可以使用线段树搞搞事情,很意外。

首先,我们对每种代价进行建树,然后从大到小对长度进行排序,接着遍历一遍,枚举桌腿高度,不断添加删除的边,因为所有高于当前桌腿高度的都要删除(真正的从线段树中删除),然后判断低于当前桌腿高度的这些桌腿的数目是否少于当前高度的数量,如果是的话,直接更新结果,如果不是,那么我们需要从低于当前桌腿高度的桌腿中查找若干个代价最低的桌腿删除(并不是真的删除,只是查找线段树中的这部分结果而已),这个过程十分适合用线段树处理,所以呢,这个题就酱紫过了。

总而言之,这个题是一道可以用线段树优化的枚举贪心问题。

写这个代码时我遇见了一个极其隐秘的 bug ,但是这个 bug 一点也不稀罕,这个 bug 是由于宏定义的 MIN 导致的。

我定义了一个 MIN(a,b) ((a)>(b)) ? (b):(a) 的宏定义,然后在代码中调用了,代码如下:

ans = MIN(ans, sum + query(1, MAXN, 1));

宏替换后就变成了这个,

ans = ans > (sum + query(1, MAXN, 1)) ? (sum + query(1, MAXN, 1)) : ans;

如此一来,我的 query() 会多调用若干次,而代码中很清楚的是,这并不是单纯的访问, ld 会在访问时产生变化,影响下次的访问。

这个问题,让我找了一个多小时的 bug ,虽然我宏定义这个玩意儿时格外注意了缺少括号所可能带来的 bug ,但是我终究还是没有注意到函数重复调用上的 bug ……

╮(╯▽╰)╭哎,像我这么粗心大意的人,还是少用宏定义吧,动不动就引入了这种极度隐秘的 bug ,心塞( ⊙ o ⊙ )啊!

代码

#include <cstdio>
#include <iostream>
#include <algorithm>

#define lson k << 1
#define rson k << 1 | 1

using namespace std;

typedef long long ll;

const int MAXN = 1e5 + 10;
const ll INF = 0x3f3f3f3f3f3f3f3f;

template <class T>
inline void scan_d(T &ret)
{
    char c;
    ret = 0;
    while ((c = getchar()) < '0' || c > '9');
    while (c >= '0' && c <= '9')
    {
        ret = ret * 10 + (c - '0'), c = getchar();
    }
}

struct leg
{
    int l, d;
    bool operator < (const leg &b) const
    {
        return l > b.l;
    }
} L[MAXN];

struct node
{
    int c;
    ll v;
} tree[MAXN << 2];

int n;
int cnt[MAXN];      //  每种代价的数量

inline void pushup(int k)
{
    tree[k].v = tree[lson].v + tree[rson].v;
    tree[k].c = tree[lson].c + tree[rson].c;
}

void build(int l, int r, int k)
{
    if (l == r)
    {
        if (cnt[l])
        {
            tree[k].v = 1ll * (tree[k].c = cnt[l]) * l;
        }
        return ;
    }
    int m = (l + r) >> 1;
    build(l, m, lson);
    build(m + 1, r, rson);
    pushup(k);
}

int ld, flag;
void update(int l, int r, int k)
{
    if (flag == -1)
    {
        tree[k].c--, tree[k].v -= ld;
    }
    else
    {
        tree[k].c++, tree[k].v += ld;
    }

    if (l == r)
    {
        return ;
    }
    int m = (l + r) >> 1;
    if (ld <= m)
    {
        update(l, m, lson);
    }
    else
    {
        update(m + 1, r, rson);
    }
}

ll query(int l, int r, int k)
{
    if (l == r)
    {
        return 1ll * ld * l;
    }

    int m = (l + r) >> 1;
    if (tree[lson].c >= ld)
    {
        return query(l, m, lson);
    }
    else
    {
        ld -= tree[lson].c;
        return tree[lson].v + query(m + 1, r, rson);
    }
}

int main()
{
    scan_d(n);
    ll ans = INF, sum = 0;
    for (int i = 1; i <= n; i++)
    {
        scan_d(L[i].l);
    }
    for (int i = 1; i <= n; i++)
    {
        scan_d(L[i].d);
        cnt[L[i].d]++;
    }

    build(1, MAXN, 1);
    sort(L + 1, L + n + 1);

    ll tmp = 0;
    for (int i = 1, j; i <= n; i = j + 1)
    {
        if (sum >= ans)
        {
            break;
        }

        sum += tmp;
        j = i;
        tmp = ld = L[i].d;
        flag = -1;
        update(1, MAXN, 1);

        while (j < n && L[j + 1].l == L[i].l)
        {
            tmp += (ld = L[++j].d);
            flag = -1;
            update(1, MAXN, 1);
        }

        int num = j - i;    //  当前高度桌腿的数目
        if (n - j <= num)
        {
            ans = min(ans, sum);
            break;
        }
        else
        {
            ld = n - j - num;
            ans = min(ans, sum + query(1, MAXN, 1));
        }
    }

    cout << ans << endl;

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值