洛谷P3253 [JLOI2013] 删除物品

28 阅读4分钟

原题:[www.luogu.com.cn/problem/P32…]

题面:

P3253 [JLOI2013] 删除物品

题目描述

箱子再分配问题需要解决如下问题:

  1. 一共有 NN 个物品,堆成 MM 堆。

  2. 所有物品都是一样的,但是它们有不同的优先级。

  3. 你只能够移动某堆中位于顶端的物品。

  4. 你可以把任意一堆中位于顶端的物品移动到其它某堆的顶端。若此物品是当前所有物品中优先级最高的,可以直接将之删除而不用移动。

  5. 求出将所有物品删除所需的最小步数。删除操作不计入步数之中。

  6. 这是一个比较难解决的问题,这里你只需要解决一个比较简单的版本:不会有两个物品有着相同的优先级,且 M=2M=2

输入格式

第一行是包含两个整数 N1N_1, N2N_2 分别表示两堆物品的个数。接下来有 N1N_1 行整数按照从顶到底的顺序分别给出了第一堆物品中的优先级,数字越大,优先级越高。再接下来的 N2N_2 行按照同样的格式给出了第二堆物品的优先级。

输出格式

对于每个数据,请输出一个整数,即最小移动步数。

输入输出样例 #1

输入 #1

3 3
1
4
5
2
7
3

输出 #1

6

说明/提示

1N1+N21000001\leq N_1+N_2\leq 100000

SolutionSolution

说实话一开始没有想出来,想到的是看能不能维护两个树状数组,分别看当前要删除的最大数的逆序对,然后卡在移动不知道怎么做了。唉唉,真的好菜。

回到题目,我们很显然的能看出移动策略,只要每次将当前最大值所在的栈中在它上面的数移到另一个栈里面就可以了,然后最终的结果就是每一次移动的元素数量的和。

但是如果直接暴力模拟的话,显然复杂度是 O((N1+N2)2)O((N_1+N_2)^2) 的,对于 1e51e5 的数量级不可行,所以我们要考虑优化这个移动的过程。

我们观察每一次将一个栈顶端的一些元素移动到另一个栈顶端的过程,发现移动之后,这些元素的顺序反了过来,原本是从上到下,移动后变成了从下到上。如果我们将两个栈的顶端拼在一块,形成一个新的序列,那么可以发现不论怎么移动,元素之间的相对位置是不会改变的。而对于每次移动,我们又只要求其中一个栈顶端到最大值之间的元素个数,所以我们可以用一个 pospos 来记录当前两个栈之间的位置,然后每次统计这个位置与当前最大值位置之间的元素之和。

对于这种区间求和,使用线段树和树状数组都可以,这里使用树状数组 (因为线段树码量太大不想打) ,因为我现在在练树状数组。

CodingCoding

#include <iostream>
#include <cstring>
#include <iomanip>
#include <cmath>
#include <vector>
#include <algorithm>
#include <climits>
using namespace std;

#define ll long long
#define ull unsigned long long
#define debug(x) cout << #x << "=" << x << "\n";

int n1, n2;
const int maxn = 1e5 + 10;
struct Node
{
    int val, pos;
};

class fenwick_tree
{
private:
    int n;
    vector<ll> bit;

public:
    fenwick_tree(const int n)
    {
        this->n = n;
        bit.resize(n + 1, 0);
    }

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

    void update(int idx, ll val)
    {
        while (idx <= n)
        {
            bit[idx] += val;
            idx += lowbit(idx);
        }
    }

    ll query(int idx)
    {
        ll sum = 0;
        while (idx)
        {
            sum += bit[idx];
            idx -= lowbit(idx);
        }

        return sum;
    }

    ll query_range(int l, int r)
    {
        return query(r) - query(l - 1);
    }

    void initialize(vector<ll> &arr)
    {
        for (int i = 1; i <= n; i++)
            update(i, arr[i]);
    }
};

int main()
{
    ios::sync_with_stdio(false);
    cin.tie(nullptr);

    cin >> n1 >> n2;
    int total = n1 + n2 + 1; // 总元素数量加上1(空出一格用于维护两个栈顶之间的位置)
    vector<int> vals(total + 1);
    for (int i = n1; i >= 1; i--)
        cin >> vals[i];
    for (int i = n1 + 2; i <= n1 + n2 + 1; i++)
        cin >> vals[i];

    vals[n1 + 1] = INT_MIN;
    vector<Node> ord(total);

    for (int i = 1; i <= total; i++)
        ord[i - 1] = {vals[i], i};

    sort(ord.begin(), ord.end(), [](const Node &x, const Node &y)
         { return x.val > y.val; });

    vector<ll> init(total + 1, 1); // 将树状数组每个对应位置赋值为1
    init[n1 + 1] = 0;              // 分隔位置不算
    fenwick_tree ft(n1 + n2 + 1);
    ft.initialize(init);
    ll ans = 0;
    int pre_pos = n1 + 1;

    for (auto [val, now_pos] : ord)
    {
        if (val == INT_MIN)
            continue;

        if (now_pos < pre_pos)
            ans += ft.query_range(now_pos + 1, pre_pos);
        else
            ans += ft.query_range(pre_pos, now_pos - 1);

        ft.update(now_pos, -1);
        pre_pos = now_pos;
    }

    cout << ans;

    return 0;
}

总体时间复杂度:O((N1+N2)log(N1+N2))O((N_1+N_2)log(N_1+N_2)) ,主要在排序和树状数组的排序和查询上。