河南省13届省赛-J.甜甜圈

链接:https://ac.nowcoder.com/acm/contest/17148/J
来源:牛客网

艾洛喜欢吃甜食,他有n个甜甜圈,现在叠成了两叠(如下图所示),第一叠有n1个,第二叠有n2个(n1+n2=n),要解决的问题如下:

  • 每个甜甜圈都有一个唯一的甜度值sis_isi,甜度值两两不同;
  • 每次艾洛可以把任意一叠位于顶端的一个甜甜圈移动到另一叠顶端,若该甜甜圈是当前所有甜甜圈中最甜的(甜度值最大),那么艾洛不会移动甜甜圈,而是直接吃掉;
请你求出艾洛吃完所有甜甜圈的最小移动步数
 
输入
3 3
1 4 5
2 7 3
输出
6

首先拿到这道题目一开始的想法绝对是暴力,但是我们很容易发现暴力会被卡成n2(max1, maxn-1 ......  maxn-2, max2)就是将最大最小交替放置。

考虑优化。我们真的需要知道所有元素的绝对顺序么?似乎是不必要的。我们只需要知道当前最大元素上面有多少个元素即可,以及判断这个最大元素是在左区间还是右区间。

那么求出当前元素之前有多少个元素该如何求呢?考虑树状数组。树状数组可以在logn时间内求出当前元素之前有多少个元素,而且支持单点修改。非常适合这道题目。

那么左右区间该如何个维护呢。我们稍微模拟一下这个过程可以发现,每次我们都会将当前最大的元素作为左右区间的分割点。那么我们只需要在每次算出贡献之后把区间的分割点修改即可。

但是考虑到左右区间都是自顶向上,所以在存左区间时需要倒序存储。

具体代码如下:

#include <bits/stdc++.h>
using namespace std;
const int maxn = 2e5+10;
typedef long long ll;

int n, m;
int tree[maxn];

int lowbit(int x) {
    return x & (-x);
}

void add(int pos, int val) {
    for (pos ; pos < maxn; pos += lowbit(pos)) {
        tree[pos] += val;
    }
}

int query(int pos) {
    int sum = 0;
    for (pos ; pos >= 1; pos -= lowbit(pos)) {
        sum += tree[pos];
    }
    return sum;
}

struct node {
    int val, id;
} a[maxn];

bool cmp(node a1, node a2) {
    return a1.val > a2.val;
}

int main() {
    scanf("%d%d", &n, &m);
    int cnt = 0;
    for (int i = n; i >= 1; -- i) {
        a[++ cnt].id = i;
        scanf("%d", &a[cnt].val);
    }
    for (int i = 1; i <= m; ++ i) {
        a[++cnt].id = i + n;
        scanf("%d", &a[cnt].val);
    }
    sort(a+1, a+1+cnt, cmp); ///大到小排序
    int bound = n; ///划分左右区间
    for (int i = 1; i <= cnt; ++ i) {
        add(i, 1);
    }
    ll ans = 0;
    for (int i = 1; i <= cnt; ++ i) {
        int p = a[i].id;
        if (p > bound) { ///右区间
            ans += query(p) - query(bound) - 1;
        }
        else { ///左区间
            ans += query(bound) - query(p);
        }
        add(p, -1);
        bound = p; ///修改分割点
    }
    printf("%lld
", ans);
    return 0;
}
原文地址:https://www.cnblogs.com/Vikyanite/p/14840672.html