代码源:736、上帝的集合

104 阅读3分钟

本文已参与[新人创作礼]活动,一起开启掘金创作之路 logo.png

题目描述

这是4月14日代码源div2的每日一题。

上帝的集合 - 题目 - Daimayuan Online Judge

题目描述

现在上帝有一个空集合,现在他命令你为他执行下列三种操作 n 次,他每次会给你一个操作类型 op。

操作1:向集合中插入一个整数 x;

操作2:将集合中所有的数加上 x;

操作3:输出集合中最小的数,并从集合中将他删除,如果存在多个最小的整数,任意选择一个即可;

输入描述

第一行输入一个整数 n;

接下来的 n 行,每行的输入如下所示。第一个数代表 op,如果 op=1 或 op=2,第二个数代表 xi:

1 xi

2 xi

3

输出描述

如果 op=3,请输出集合中的最小值。

样例输入

7
1 2
1 1
3
1 3
2 5
3
3

样例输出

1
7
8

数据范围

2≤n≤10^6, 1≤xi≤10^12

问题解析

第一个操作:向集合中插入值。这个操作并不难,有非常多的方法都能做到。

第三个操作:输出最小值并删除。我们利用优先队列或mulset等容器都可以很方便的做到。

问题就是第二个操作,怎么能在短时间内把所有的值都加上1?说到区间修改有的人可能会想到线段树,确实,线段树是个可行的方法,加上lz标记后,我们可以在logn的时间内做到区间修改,并且只要每个点存储的都是区间内的最小值,我们就可以在logn的时间内完成区间修改,logn的时间内找到最小值并将其删除,logn的时间内插入一个元素。

但是lz标记这种东西写起来我觉得太麻烦了!我们有没有办法能做到更简便的完成操作二?

当然是有的,我才不想跑去写lz标记。我们可以假装把所有的元素都加上x,我们用一个变量add存下所有加在全体的x,只要第三次操作输出值的时候把add加上那个值即可。那么有个问题,比如我前面先插入几个元素后全体加10,然后插入了一个很小的元素,这样操作三会输出这个很小的元素,输出的时候会加上add,但是这个元素在逻辑上并没有加上10,怎么办?好解决,我们只要插入的时候,把元素预先减去add再插入即可,比如我这里插入的11,但实际插入的是1。这样在再输出的也是11。至于操作一和三,我们只要找一个能很快找到最小值的容器就行。

AC代码

1:优先队列(小根堆)

#include<iostream>
using namespace std;
#include<vector>
#include<algorithm>
#include<math.h>
#include<set>
#include<numeric>
#include<string>
#include<string.h>
#include<iterator>
#include<map>
#include<unordered_map>
#include<stack>
#include<list>
#include<queue>
#include<iomanip>

#define endl '\n';
typedef long long ll;
typedef pair<ll, ll>PII;
const int N = 1e6 + 10;


int main()
{
    ios_base::sync_with_stdio(false);
    cin.tie(nullptr);
    cout.tie(nullptr);
    ll t,add=0;
    priority_queue<ll,vector<ll>,greater<ll>>que;
    cin >> t;
    while (t--)
    {
        int st;
        cin >> st;
        if (st == 1)
        {
            ll x;
            cin >> x;
            que.push(x-add);
        }
        else if (st == 2)
        {
            ll x;
            cin >> x;
            add += x;
        }
        else
        {
            cout << que.top()+add << endl;
            que.pop();
        }
    }
    
    return 0;
}

2.线段树

这个有点特殊,因为线段树是很难直接加入一个新的点的,但我们可以提前开好空间,这里一共操作数是10^6,假设这全是插入操作,加上保险起见,我们最多开他一个4* 1e6+40的线段树,一共有1e6+10个叶子,为了不影响结果,我们把所有的叶子都整成很大的值。然后照着这些位置一个个插入值即可,至于删除操作,我们直接把那个叶子的值改成很多的值即可。

#include<iostream>
using namespace std;
#include<vector>
#include<algorithm>
#include<math.h>
#include<set>
#include<numeric>
#include<string>
#include<string.h>
#include<iterator>
#include<map>
#include<unordered_map>
#include<stack>
#include<list>
#include<queue>
#include<iomanip>

#define endl '\n';
typedef long long ll;
typedef pair<ll, ll>PII;
const int N = 1e6 + 10;
ll f[4 * N],len=0;
  
void inserttree(ll k, ll l, ll r, ll x,ll y)
{
    if (l == r)
    {
        f[k] = y;
        return;
    }
    ll m = (l + r) / 2;
    if (x <= m)inserttree(k + k, l, m, x, y);
    else inserttree(k + k + 1, m + 1, r, x, y);
    f[k] = min(f[k + k], f[k + k + 1]);
}

ll calc(ll k, ll l, ll r)
{
    if (l == r)
    {
        ll ans = f[k];
        f[k] = 1e15;
        return ans;
    }
    ll m = (l + r) / 2,res;
    if (f[k + k]<= f[k + k + 1])res = calc(k + k, l, m);
    else res = calc(k + k + 1, m + 1, r);
    
    f[k] = min(f[k + k], f[k + k + 1]);
    return res;
}

int main()
{
    ios_base::sync_with_stdio(false);
    cin.tie(nullptr);
    cout.tie(nullptr);
    ll t,add=0;
    cin >> t;
    memset(f, 0x3f3f, sizeof f);
    int a = 0;
    while (t--)
    {
        ll st, x;
        cin >> st;
        if (st == 1)
        {
            cin >> x;
            len++;
            inserttree(1, 1, 1000000, len, x - add);
        }
        else if (st == 2)
        {
            cin >> x;
            add += x;
        }
        else
        {
            cout << 1LL*(calc(1, 1, 1000000)+add) << endl;
        }
    }
    
    return 0;
}