You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

148 lines
5.3 KiB

2 years ago
/*
x x1 0 1
1,1,1,
*/
#include <iostream>
#include <string.h>
#include <stdio.h>
#include <vector>
#include <map>
#include <queue>
#include <algorithm>
#include <math.h>
#include <cstdio>
//宏定义左右儿子
#define ls u << 1
#define rs (u << 1) | 1
using namespace std;
const int N = 2e5 + 5;
int n, m, k;
struct Query {
int op, l, r;
} q[N];
int a[N];
struct Node {
int l, r;
int val; // 0:升序 1降序 -1:无操作
int sum;
} tr[N << 2];
//线段树取管辖区间长度适用于lazy tag统一变化时sum的快速计算
int len(int u) {
return tr[u].r - tr[u].l + 1;
}
//向父节点更新信息写成Node &的方式目前看来是最合理的办法
void pushup(Node &c, Node &a, Node &b) {
c.sum = a.sum + b.sum;
}
//更新lazy tag标识
void pushdown(int u) {
if (tr[u].val == -1) return; //如果没有下传标识,啥也不干
tr[ls].sum = len(ls) * tr[u].val; //如果有下传标识左儿子区间内所有数字都要加上tr[u].add,sum值为累加
tr[rs].sum = len(rs) * tr[u].val; //如果有下传标识右儿子区间内所有数字都要加上tr[u].add,sum值为累加
tr[ls].val = tr[rs].val = tr[u].val; //左右儿子都修改标识为val,一次只更新一层
tr[u].val = -1; //标识已处理
}
//构建线段树,x:当前两分取到的值用于建立线段树时判断每个叶子的初始值是1还是0
// a[l]>=x tr[u].sum=1
// a[l]<x tr[u].sum=0
// 本质上是记录了在区间内有多少个大于等于目标值的数字个数
void build(int u, int l, int r, int x) {
tr[u] = {l, r, -1, 0}; //注意这里初始化val=-1,否则会被认为需要将lazy tag=0,将区间内所有值设置为0
if (l == r) {
tr[u].sum = a[l] >= x; //大于等于x的都设置为1,小于x的设置为0
return;
}
int mid = (l + r) >> 1;
build(ls, l, mid, x), build(rs, mid + 1, r, x);
//更新父节点信息
pushup(tr[u], tr[ls], tr[rs]);
}
//查询区间内数字1的个数
int query(int u, int l, int r) {
if (l > tr[u].r || r < tr[u].l) return 0; //不在范围内的返回0
if (l <= tr[u].l && r >= tr[u].r) return tr[u].sum; //命中直接返回
// lazy tag下传
pushdown(u);
//计算左儿子结果+右儿子结果
return query(ls, l, r) + query(rs, l, r);
}
//区间修改
void modify(int u, int l, int r, int v) {
//特判边界,防止越界
if (l > r) return;
//如果命中区间对区间的lazy tag和sum进行计算修改
if (l <= tr[u].l && r >= tr[u].r) {
tr[u].val = v;
tr[u].sum = v * len(u);
return;
}
//没有命中区间,需要递归向左右儿子传递修改消息
pushdown(u);
//修改左区间
if (l <= tr[ls].r) modify(ls, l, r, v);
//修改右区间
if (r >= tr[rs].l) modify(rs, l, r, v);
//因为子区间内容修改,需要向父节点更新统计信息
pushup(tr[u], tr[ls], tr[rs]);
}
bool check(int x) {
//每次本新构建线段树
build(1, 1, n, x);
//根据输入排序的次序范围由大到小1 由小到大0
for (int i = 1; i <= m; i++) {
int l = q[i].l, r = q[i].r, op = q[i].op;
//到一个大于等于x就是1小于x就是0的线段树中去查找
int c = query(1, l, r); //查询l,r之间数字1的个数
if (op == 1) //由大到小排序,所有1在前所有0在后
modify(1, l, l + c - 1, 1), modify(1, l + c, r, 0);
else //由小到大排序,所有0在前所有1在后
modify(1, l, r - c, 0), modify(1, r - c + 1, r, 1);
}
//经过上面的这些操作,
//如果第k个位置上是1说明现在第k个位置上对应原数组中的数字是大于等于答案x的应该试一下大的。
//如果第k个位置上是0说明现在第k个位置上对应原数组中的数字是小于答案x的应该试一下小的。
return query(1, k, k) == 1;
}
int main() {
//优化读入
ios::sync_with_stdio(false);
cin.tie(0);
cout.tie(0);
int T;
cin >> T;
while (T--) {
cin >> n >> m;
//原始序列
for (int i = 1; i <= n; i++) cin >> a[i];
//排序的动作与范围
for (int i = 1; i <= m; i++) cin >> q[i].op >> q[i].l >> q[i].r;
//查询第k个位置上的数字是多少
cin >> k;
int l = 1, r = n; //左右边界最小是1最大是n
while (l <= r) {
//假设第k个位置上的数为mid看看会发生什么
int mid = (l + r) >> 1; //二分查找
if (check(mid)) //如果mid符合条件
l = mid + 1; //似乎由于check是用==作为判断条件的l不能取到mid,需要二刷时仔细思考下
else
r = mid - 1;
}
printf("%d\n", l - 1);
}
return 0;
}