2022-04-14每日刷题打卡
代码源——每日一题
上帝的集合 - 题目 - 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。至于操作一和三,我们只要找一个能很快找到最小值的容器就行。
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;
}
CodeForces——线段树专题
B - Segment Tree, part 1 - Codeforces
This problem is the reversed version of the previous one. There was a permutation pi of n elements, for each ii we wrote down the number ai, the number of j such that j<i and pi<pj. Restore the original permutation for the given ai.
Input
The first line contains the number n (1≤n≤10^5), the second line contains n numbers ai. It is guaranteed that ai were obtained from some permutation using the procedure described in the statement.
Output
Print n numbers, the original permutation.
Example
input
5
0 1 1 0 3
output
4 1 3 5 2
这题是说,有这么一个数组a,他有个对应的数组b,b的每一位是a的对应位置上,它的左边有多少个大于他的个数,现在他给你了这个数组b,让你求出数组a。
用线段树来写就是,我们从右往左遍历数组b,准备一个线段树,初始叶子都是1。我们每次在线段树里找从右往左数,找第b[i]个1的位置。之后把那个位置的叶子变成0。
为什么这样可以得到结果?首先,我们是从右往左数第b[i]个1的个数,右边的点都是大于左边的点的。而且因为之前数过的叶子都会变成0,说明变成0的叶子在结果的a数组里应该出现在我们当前找的位置的右边,而我们找的是左边有多少个大于我们的个数。所以就要从右往左找第b[i]个叶子的位置。
#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<int, int> PII;
const int N = 100050;
int f[4 * N], a[N], n;
void buildtree(int k, int l, int r)
{
if (l == r)
{
f[k] = 1;
return;
}
int m = (l + r) / 2;
buildtree(k + k, l, m);
buildtree(k + k + 1, m + 1, r);
f[k] = f[k + k] + f[k + k + 1];
}
void revise(int k, int l, int r, int x)
{
if (l == r)
{
f[k] = 0;
return;
}
int m = (l + r) / 2;
if (x <= m)revise(k + k, l, m, x);
else
revise(k + k + 1, m + 1, r, x);
f[k] = f[k + k] + f[k + k + 1];
}
int quire(int k, int l, int r, int x)
{
if (l == r)
{
return l;
}
int m = (l + r) / 2;
if (f[k + k + 1] >= x)return quire(k + k + 1, m + 1, r, x);
else return quire(k + k, l, m, x - f[k + k + 1]);
}
int main()
{
cin >> n;
for (int i = n; i >= 1; i--)
{
cin >> a[i];
a[i]++;
}
buildtree(1, 1, n);
vector<int>v(n);
for (int i = 1; i <= n; i++)
{
v[i - 1] = quire(1, 1, n, a[i]);
revise(1, 1, n, v[i - 1]);
}
for (int i = n - 1; i >= 0; i--)cout << v[i] << " ";
return 0;
}