#include #include #include using namespace std; typedef long long LL; //快读 LL read() { LL x = 0, f = 1; char ch = getchar(); while (ch < '0' || ch > '9') { if (ch == '-') f = -1; ch = getchar(); } while (ch >= '0' && ch <= '9') { x = (x << 3) + (x << 1) + (ch ^ 48); ch = getchar(); } return x * f; } const int N = 200010; LL ans[N]; //每个询问的答案 int n, m; //原始n个数字,共m个操作 //线段树结构体 #define ls u << 1 #define rs u << 1 | 1 struct Node { int l, r; //区间范围 LL lazy; //区间修改懒标记 LL sum; //区间和 } tr[N << 2]; //向上更新统计信息:区间和 void pushup(int u) { tr[u].sum = tr[ls].sum + tr[rs].sum; } //向下传递区间修改标记 void pushdown(int u) { //如果懒标记为0,没有可以传递 if (!tr[u].lazy) return; //向下传递lazy标志 tr[ls].lazy += tr[u].lazy, tr[rs].lazy += tr[u].lazy; //加法有叠加性 //用lazy通过计算,更新左右儿子的sum值 int l = tr[u].l, r = tr[u].r; int mid = (l + r) >> 1; tr[ls].sum += tr[u].lazy * (mid - l + 1); tr[rs].sum += tr[u].lazy * (r - mid); // lazy标记清零 tr[u].lazy = 0; } //构建线段树 void build(int u, int l, int r) { tr[u] = {l, r, 0, 0}; if (l == r) return; int mid = (l + r) >> 1; build(ls, l, mid), build(rs, mid + 1, r); } //区间更新 //之所以参数起名为[ql,qr]因为这是查询区间,而不是构建区间 void modify(int u, int ql, int qr, LL c) { int l = tr[u].l, r = tr[u].r; //将变量名l,r保留下来给tr[u].l,tr[u].r if (ql <= l && qr >= r) { // u结点的管辖范围[tr[u].l~tr[u].r]完整命中 tr[u].lazy += c; //区间修改加上lazy标记,这样就不用现在就逐层下传了 tr[u].sum += c * (r - l + 1); //对统计信息进行更新,完成本层的统计信息更新 return; } //下面要进行分裂,分裂前需要pushdown pushdown(u); int mid = (l + r) >> 1; if (ql <= mid) modify(ls, ql, qr, c); //与左儿子区间有交集,递归左儿子 if (mid < qr) modify(rs, ql, qr, c); //与右儿子区间有交集,递归右儿子 //向上更新统计信息 pushup(u); } //查询区间sum和 LL query(int u, int ql, int qr) { int l = tr[u].l, r = tr[u].r; if (ql <= l && r <= qr) return tr[u].sum; //完整命中,返回区间和 LL res = 0; //分裂前需要pushdown pushdown(u); int mid = (l + r) >> 1; if (ql <= mid) res += query(ls, ql, qr); //与左儿子区间有交集,递归左儿子 if (mid < qr) res += query(rs, ql, qr); //与右儿子区间有交集,递归右儿子 return res; } //整体二分的查询结构体 struct Q { int op, l, r; LL k; int id; } q[N], q1[N], q2[N]; //整体二分 void solve(int l, int r, int ql, int qr) { if (ql > qr) return; //无效查询区间,防RE if (l == r) { for (int i = ql; i <= qr; i++) if (q[i].op == 2) ans[q[i].id] = l; //二分标识答案 return; } int mid = (l + r) >> 1; int nl = 0, nr = 0; for (int i = ql; i <= qr; i++) { if (q[i].op == 1) { //增加 if (q[i].k > mid) { modify(1, q[i].l, q[i].r, 1); //用线段树指定区全都+1 q1[++nl] = q[i]; //加入左增加操作 } else q2[++nr] = q[i]; //加入右增加操作 } else { //查询 LL t = query(1, q[i].l, q[i].r); if (t >= q[i].k) q1[++nl] = q[i]; else { q[i].k -= t; q2[++nr] = q[i]; } } } //有加就有减 for (int i = ql; i <= qr; i++) if (q[i].op == 1) if (q[i].k > mid) modify(1, q[i].l, q[i].r, -1); //合并到q数组 for (int i = ql; i < ql + nl; i++) q[i] = q1[i - ql + 1]; for (int i = ql + nl; i <= qr; i++) q[i] = q2[i - ql - nl + 1]; //递归左右 solve(mid + 1, r, ql, ql + nl - 1), solve(l, mid, ql + nl, qr); } int main() { //原序列n个数字,共m个操作 n = read(), m = read(); //构建线段树 build(1, 1, n); int qc = 0; //查询的个数 for (int i = 1; i <= m; i++) { int op = read(), l = read(), r = read(), k = read(); if (op == 2) //查询操作 q[i] = {op, l, r, k, ++qc}; else // 增加操作 op=1 // 1 l r k : op=1 增加,[l,r]:增加k q[i] = {op, l, r, k}; } //整体二分 solve(-1e9, 1e9, 1, m); //输出 for (int i = 1; i <= qc; i++) printf("%lld\n", ans[i]); return 0; }