emm……,第一次发一道紫题的题解。
大体思路:二分+树状数组
废话不多说,直接开始。
一、题目大意
有一个长度为 n 的正整数序列 A、 m 个区间 [li, ri] 和两个正整数 a, k。这 m 个区间里选出恰好 k 个区间,并对每个区间执行一次区间加 a 的操作。
求怎么选择区间才能让操作后最大化min Ai。
二、思路详解
Part One 想到二分
其实,我一看到题目,也没有想出什么思路,于是再次认真地审了一下题目,想从题目描述中找到解题思路。不一会,我看到了一句话:“操作后的序列的最小值尽可能的大”,最小值最大,我们可以想道什么?
当然是:二分!
Part Two 二分的判断函数——check
check的含义
按照二分的套路,我们的check函数实现的意义是:判断取得的值是否能在用恰好k个区间将这个正整数序列的最小值提到mid以上。
那么,我就想这样:先打出一个暴力的模拟出来,再优化。
记录小于mid的数的下标
暴力的模拟很简单:首先从左到右扫每一个数,当找到1个数是小于mid的,就记录下它的下标于s数组中。
接着,我们就要想:通过区间去搞定每一个小于mid的数。
枚举区间
于是,我们开始枚举每个区间,要想搞定一个数,那么,我们就要先保证加的区间包含这个数。
在符合这个条件之下,我们就要贪心的想:区间越大,那么结果肯定越优。所以,我们枚举的区间在包含这个数的条件下还要长度最大。
增加
枚举到了区间,我们就要进行增加操作,这个就直接加就行了。
三、优化
按照我上面的去模拟,你会发现时间复杂度特别高,在20-60 分左右,如何优化呢?
枚举区间
用大根堆去维护区间最优,这样可以避免每次O(n)的去寻找最优的区间。
增加操作
我们暴力的去增加数的大小,时间最差是n,但是,有一类数据结构,专门应对区间加减和查询,是什么呢?
树状数组!(可能线段树也可以,但我不会)
这样,我们便可以用树状数组去维护增加操作。
四、代码及其注释
#include <bits/stdc++.h>
using namespace std;
const int N = 2e5 + 5;
int T, n, m, k, val, id, cnt, j, a[N], s[N], cf[N], BIT[N];
struct Node {
int l, r;
}seque[N];
bool cmp(Node backup1, Node backup2) {
return backup1.l < backup2.l;
}
template <typename T> void read(T &x){
x = 0; int f = 1; char ch = getchar();
while (!isdigit(ch)) {if (ch == '-') f = -1; ch = getchar();}
while (isdigit(ch)) {x = x * 10 + ch - '0'; ch = getchar();}
x *= f;
}
int lowbit(int x) { return x & -x; }
void update(int l, int r) {for (; l <= n; l += lowbit(l)) BIT[l] += r;}
int GetSum(int x) {
int ans = 0;
for (; x; x = x - lowbit(x)) ans += BIT[x];
return ans;
}
bool check(int x) {
priority_queue<int> q;
for (int i = 1;i <= n;i++)
if (a[i] < x)
s[++id] = i;
for (int i = 1;i <= id;i++) {
for (;j <= m;j++) {
if (seque[j].l <= s[i]) {
q.push(seque[j].r);
} else break;
}
while(GetSum(s[i]) < x) {
if (k <= cnt || q.empty()) return 0;
int R = q.top();
q.pop();
update(s[i], val), update(R + 1, -val);
cnt++;
}
}
return 1;
}
int main() {
read(T);
while(T--) {
read(n), read(m), read(k), read(val);
for (int i = 1;i <= n;i++) read(a[i]), cf[i] = a[i] - a[i - 1];
for (int i = 1;i <= m;i++) read(seque[i].l), read(seque[i].r);
sort(seque + 1, seque + m + 1, cmp);
int l = 1, r = val * k + *min_element(a + 1, a + n + 1);
while(l < r) {
int mid = l + r + 1 >> 1;
id = cnt = 0, j = 1;
for (int i = 1;i <= n;i++) BIT[i] = 0;
for (int i = 1;i <= n;i++) update(i, cf[i]);
if (check(mid)) l = mid;
else r = mid - 1;
}
printf("%d", l);
puts("");
}
return 0;
}
用时:295ms,还算快吧。
好了,完美撒花!
|