(1)首先要想到贪心,若想要满足最小数最大,那么我们尽量选区间长度长的修改方法修改,这样同是加a,但是长度长的加的更多,明显比较优越。
(2)如何找到最大区间,我们可以在输入后对其左端点排序(原因等会说),令最小的左端点排在前面,然后要用时再依次插入优先队列,而优先队列即是要用来将左端点满足条件且最大区间排在前面
(3)我们可以二分枚举最小的最大值,然后再从1——n循环,如果a[i]还小于mid,就说明还未到最大,可以继续修改区间使其最大,如果大于或等于就说明a[i]经过修改区间后不是最大的最小值,跳到下一个a[i], 如果最后都没有,就加大mid。
(4)这里要用到区间修改,其次每修改一次还要查询最小值,所以是树状数组的区间修改,单点查询
首先排序时如果判到两个数的左端点相同,那么就按右端点排,反正就把区间大的放前。同样优先队列的运算符重载也一样,如果右端点一样,就把左端点最小的放前面。
int cmp(jj x, jj y) { //按左端点最小的排
if (x.qi == y.qi)
return x.zhong > y.zhong; //起点相同按终点排
else
return x.qi < y.qi; //按起点排
}
struct node2 { //定义优先队列,实现每次拿出的右端点都最大
int l, r;
bool operator<(const node2 &x) const {
if (r != x.r) {
return r < x.r; //右端点排序
} else {
return l < x.l;
}
}
};
while (o[j].qi <= i && j <= m) {
node2 rr;
rr.l = o[j].qi;
rr.r = o[j].zhong;
q.push(rr);
j++;
}
如上方代码我们可以知道,我们的起点必须要小于等于i,因为这样才可以令每次改变都可以改变第i个数,从而达到第i个数一定最大,而按左端点排后,如果当前j号方法起点不满足<=i,那么后面的方法一定也不行(因为左端点从小到大排过),所以直接跳出循环。
最小值假设最大的最小值不会改变受区间任何改变,那么就是 a [ 1 — n ] a[1—n] a[1—n]最小值。最大值假设每一次都要改变最大的最小值,那么则是 a [ 1 — n ] a[1—n] a[1—n]最小值 + k * a(即k次它都加了a)
long long l = 1, r = 0x3f3f3f3f;
for (int i = 1; i <= n; i++) {
scanf("%d", &A[i]);
r = min(r, (long long)A[i]);
l = min(l, (long long)A[i]);
}
r = r + a * k;
(3)还有就是在循环cheak里面的一些细节,详细看注释:
bool cheak(long long s) {
memset(BIT, 0, sizeof(BIT));
for (int i = 1; i <= n; i++) {
update(i, A[i] - A[i - 1]); //差分数组插入
}
priority_queue<node2> q; // q放入的是目前要改变的区间,此区间满足最大
int ans = 0;//改变了几个区间
int j = 1;//选第几个改变区间方法
for (int i = 1; i <= n; i++) {
while (o[j].qi <= i && j <= m) {//如果j号改变区间方法可以改现在a[i]的且还没把方法枚举完就插入
node2 rr;
rr.l = o[j].qi;
rr.r = o[j].zhong;
q.push(rr); //因为o数组已经按照l排序,剩下再用重载让l最小且r最大的放前面,最后得到的是满组条件且最大区间
j++;
}
while (Sum(i) < s) { //如果此时最小值小于mid则证明可继续修改,如果大于或等于了则证明mid取小了,要增大
if (q.empty() || ans == k) { //若无区间可查或已经改变了k个区间了,就证明mid不可取跳出
return 0;
}
node2 ll = q.top();
q.pop();
update(ll.l, a);
update(ll.r + 1, -a);
ans++;//改变次数++
}
}
return 1;
}
#include
#include
#include
#include
#include
using namespace std;
int n, m, k, a, A[200005], _max, BIT[200005];
struct jj {
int qi, zhong; //起始位置,终止位置
} o[200005];
int cmp(
jj x,
jj y) { //因为越大的区间可修改的数越多,即可让改变的数最多,最小值最容易增大,所以用区间大小从大到小改
if (x.qi == y.qi)
return x.zhong > y.zhong; //起点相同按终点排
else
return x.qi < y.qi; //暂时先让起点最大的排在前面,之后再用“优先队列”排终点,使区间最大
}
struct node2 { //定义优先队列,实现每次拿出的右端点都最大
int l, r;
bool operator<(const node2 &x) const {
if (r != x.r) {
return r < x.r; //右端点排序
} else {
return l < x.l;
}
}
};
int lowbit(int x) { return x & (-x); }
void update(int k, int shu) {
for (int i = k; i <= n; i += lowbit(i)) {
BIT[i] += shu;
}
}
long long Sum(int zhong) {
long long da = 0;
for (int i = zhong; i >= 1; i -= lowbit(i)) {
da += BIT[i];
}
return da;
}
bool cheak(long long s) {
memset(BIT, 0, sizeof(BIT));
for (int i = 1; i <= n; i++) {
update(i, A[i] - A[i - 1]); //差分数组插入
}
priority_queue<node2> q; // q放入的是目前要改变的区间,此区间满足最大
int ans = 0;
int j = 1;
for (int i = 1; i <= n; i++) {
while (o[j].qi <= i && j <= m) {
node2 rr;
rr.l = o[j].qi;
rr.r = o[j].zhong;
q.push(rr); //因为o数组已经按照l排序,剩下再用重载让l最小且r最大的放前面,最后得到的是最大区间
j++;
}
while (Sum(i) < s) { //如果此时最小值小于mid则证明可继续修改,如果大于或等于了则证明mid取小了,要增大
if (q.empty() || ans == k) { //若无区间可查或已经改变了k个区间了,就证明mid不可取
return 0;
}
node2 ll = q.top();
q.pop();
update(ll.l, a);
update(ll.r + 1, -a);
ans++;
}
}
return 1;
}
int main() {
int t;
scanf("%d", &t);
while (t--) {
scanf("%d %d %d %d", &n, &m, &k, &a);
long long l = 1, r = 0x3f3f3f3f;
for (int i = 1; i <= n; i++) {
scanf("%d", &A[i]);
r = min(r, (long long)A[i]);
l = min(l, (long long)A[i]);
}
r = r + a * k;
for (int i = 1; i <= m; i++) {
scanf("%d %d", &o[i].qi, &o[i].zhong);
}
sort(o + 1, o + 1 + m, cmp);
while (l <= r) {
long long mid = (l + r) / 2;
if (cheak(mid)) {
l = mid + 1;
} else {
r = mid - 1;
}
}
printf("%lld\n", l - 1);
}
return 0;
}