【学习笔记】整体二分

例1. 给定 n n n 个数 a i a_i ai一次询问,询问区间 [ l , r ] [l,r] [l,r] 中的第 k k k 小数。

我们通常想到二分答案,然后计数在 [ l , r ] [l,r] [l,r] 区间内 ≤ x \le x x 的数的个数,再与 k k k 比较。

时间复杂度 O ( n log ⁡ n ) O(n\log n) O(nlogn)。(尽管这并不是最优秀的做法)

例2. 给定 n n n 个数 a i a_i ai多次询问,询问区间 [ 1 , n ] [1,n] [1,n] 中的第 k k k 小数。

由于区间是整个数列,所以我们直接排序一下,然后找第 k k k 个数即可,时间复杂度 O ( n log ⁡ n ) O(n\log n) O(nlogn)

例3. 给定 n n n 个数 a i a_i ai多次询问,询问区间 [ l , r ] [l,r] [l,r] 中的第 k k k 小数。

这是一道主席树,即可持久化线段树的模板题。建立权值线段树然后 r r r 版本 − l -l l 版本,线段树上二分。

实际上,我们仍然可以用二分做。

但如果对于每一个询问都二分一次,那么时间复杂度 O ( q n log ⁡ n ) O(qn\log n) O(qnlogn) 就爆炸了。

是否可以只用一次二分呢?——答案是显然的。

我们理想的一次二分应该是二分一个答案 x x x,然后利用一个数据结构快速判断答案和每一个询问的大小关系,再将询问分成 ≤ x , > x \le x,>x x,>x 的两部分,分别二分下去。

不难发现,这种假想的时间复杂度大概是带两个 l o g log log 的。

例4. 给定 n n n 个数 a i a_i ai多次询问和修改某个位置的值,询问区间 [ l , r ] [l,r] [l,r] 中的第 k k k 小数。

线段树分治??不清楚。但这仍可以二分做。

整体二分

“引”中最后假想出来的思路就是 整体二分

能用整体二分解决的题目需要满足以下性质:

  • 答案具有可二分性。
  • 修改对判断答案的贡献相互独立,即修改之间互不影响。
  • 修改如果有贡献,则是确定的贡献,不会随二分的答案产生变化。
  • 贡献满足交换律,结合律,具有广义的可加性。
  • 离线。

总体算法思路:

  • [ l , r ] [l,r] [l,r] 为二分答案的值域, [ q l , q r ] [ql,qr] [ql,qr] 为定义域,即只考虑编号在 [ q l , q r ] [ql,qr] [ql,qr] 中的修改和询问操作,这其中的询问的答案在 [ l , r ] [l,r] [l,r] 内。
  • 首先把所有操作按 时间顺序 存入数组。通常输入顺序即为操作的时间顺序。然后开始分治。
  • 在每一层的分治中,利用数据结构(常见为树状数组/线段树)统计当前查询的答案和 mid \text{mid} mid 的关系。
  • 根据查询的答案和 mid \text{mid} mid 之间的关系,分成 L , R L,R L,R 两份,分别递归。
  • 类比线段树分治,每一个分治的子问题都是互不相关的,但却共用同一个数据结构。所以这里还要涉及到回退问题。
  • l = r l=r l=r 时, [ q l , q r ] [ql,qr] [ql,qr] 中的所有询问答案即为 l l l

下面以主席树的模板题作为例题,具体阐释算法实现以及时间复杂度的计算。

  • 将输入 a i a_i ai 的操作定义为类型 1 1 1,即 q [ i ] . o p = 1 q[i].op=1 q[i].op=1。并记录下 q [ i ] q[i] q[i] 修改的位置 i i i,修改的值 a i a_i ai

  • 将查询 [ l , r ] , k [l,r],k [l,r],k 的操作定义为类型 2 2 2。并记录下询问的区间, k k k

  • 对于一个形如 ( l , r , q l , q r ) (l,r,ql,qr) (l,r,ql,qr) 的子问题,二分答案为 m i d = l + r > > 1 mid=l+r>>1 mid=l+r>>1

  • 然后处理 [ q l , q r ] [ql,qr] [ql,qr] 范围内的类型 1 1 1 操作。

    • 修改的值 ≤ m i d \le mid mid 的。

      全都扔到数据结构上。(这里使用的是树状数组)

      则树状数组上放的是 ≤ m i d \le mid mid 的且在 [ q l , q r ] [ql,qr] [ql,qr] 操作区间内的修改操作。

      再放入 L L L 数组中。

    • 修改的值 > m i d >mid >mid 的。

      这种修改对于 m i d mid mid 答案没有贡献,直接放进 R R R 数组中。

  • 对于 [ q l , q r ] [ql,qr] [ql,qr] 范围中的类型 2 2 2 操作。直接询问树状数组中 [ q [ i ] . l , q [ i ] . r ] [q[i].l,q[i].r] [q[i].l,q[i].r] 区间内的个数 c n t cnt cnt

    • 如果 c n t ≤ q [ i ] . k cnt\le q[i].k cntq[i].k

      也就是说 [ q [ i ] . l , q [ i ] . r ] [q[i].l,q[i].r] [q[i].l,q[i].r] ≤ m i d \le mid mid 的数 ≤ k \le k k 个。答案要么是 m i d mid mid 要么 < m i d <mid <mid

      根据二分原则,我们把这个询问放到 L L L 数组中。

    • 如果 c n t > q [ i ] . k cnt>q[i].k cnt>q[i].k

      类比线段树二分,我们将 q [ i ] . k q[i].k q[i].k 减去 c n t cnt cnt。然后放到 R R R 数组中。

      因为到时候处理 ( m i d + 1 , r , q l ′ , q r ′ ) (mid+1,r,ql',qr') (mid+1,r,ql,qr) 的子问题时,原本 [ q [ i ] . l , q [ i ] . r ] [q[i].l,q[i].r] [q[i].l,q[i].r] ≤ m i d \le mid mid 的数时一定会提供 1 1 1 的个数,我们没有必要每次都加入。

  • 将数据结构回退至当前层未操作前的模样。

  • 此时的 L L L 数组放的询问是真正 a n s ≤ m i d ans\le mid ansmid 的询问,放的修改是重新计算时还可能被挂到树状数组上去的修改。

    R R R 数组同理。我们已经去掉了 L L L 数组中所有修改对 R R R 中询问产生的贡献。

    现在两个数组之间的信息是完全不会有交的了。

    所以分治处理 ( l , m i d , L ) , ( m i d + 1 , r , R ) (l,mid,L),(mid+1,r,R) (l,mid,L),(mid+1,r,R)

#include <bits/stdc++.h>
using namespace std;
#define maxn 400005
#define inf 0x7f7f7f7f
int n, m;
int ans[maxn];
struct node { int op, l, r, k, id; }q[maxn], Left[maxn], Right[maxn];

namespace BIT {
	int t[maxn];
	void add( int i, int x ) { for( ;i <= n;i += i & -i ) t[i] += x; }
	int ask( int i ) { int cnt = 0; for( ;i;i -= i & -i ) cnt += t[i]; return cnt; }
}

void solve( int l, int r, int ql, int qr ) {
	if( ql > qr ) return;
	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, cntL = 0, cntR = 0;
	for( int i = ql;i <= qr;i ++ )
		if( q[i].op == 1 ) {
			if( q[i].k > mid ) Right[++ cntR] = q[i];
			else BIT :: add( q[i].id, 1 ), Left[++ cntL] = q[i];
		}
		else {
			int cnt = BIT :: ask( q[i].r ) - BIT :: ask( q[i].l - 1 );
			if( q[i].k <= cnt ) Left[++ cntL] = q[i];
			else q[i].k -= cnt, Right[++ cntR] = q[i];
		}
	for( int i = 1;i <= cntL;i ++ ) if( Left[i].op == 1 ) BIT :: add( Left[i].id, -1 );
	for( int i = 1;i <= cntL;i ++ ) q[i + ql - 1] = Left[i];
	for( int i = 1;i <= cntR;i ++ ) q[i + ql + cntL - 1] = Right[i];
	solve( l, mid, ql, ql + cntL - 1 );
	solve( mid + 1, r, ql + cntL, qr );
}

int main() {
	scanf( "%d %d", &n, &m );
	for( int i = 1, x;i <= n;i ++ ) {
		scanf( "%d", &x );
		q[i] = (node){ 1, -inf, inf, x, i };
	}
	for( int i = 1, l, r, k;i <= m;i ++ ) {
		scanf( "%d %d %d", &l, &r, &k );
		q[i + n] = (node){ 2, l, r, k, i };
	}
	solve( -inf, inf, 1, n + m );
	for( int i = 1;i <= m;i ++ ) printf( "%d\n", ans[i] );
	return 0;
}

最后来分析一下这个时间复杂度:

  • 首先是二分的答案值, O ( log ⁡ V ) O(\log V) O(logV) 的。
  • 对于同一层的分治,总共的区间定义域是 O ( n ) O(n) O(n)。类比线段树 1 , 2 , 4 1,2,4 1,2,4 的划分,但同一层的总和仍是 n n n
  • 每个数在同一层的若干个分治中最多只会被挂在数据结构上一次,然后撤销。
  • 时间复杂度应为 O ( n log ⁡ n log ⁡ V ) O(n\log n\log V) O(nlognlogV)

几道模板题

Dynamic Rankings

Dynamic Rankings

本题才是真的修改操作,即“引”中的例4 。

其实也没有什么特别的,可以类比线段树分治,一个数存在时间是一段连续的区间。

具体见代码即可明白。

#include <bits/stdc++.h>
using namespace std;
#define maxn 400005
struct node { int op, x, y, k, id; }q[maxn], L[maxn], R[maxn];
int n, m;
int ans[maxn], a[maxn];

namespace BIT {
    int t[maxn];
    void add( int i, int x ) { for( ;i <= n;i += i & -i ) t[i] += x; }
    int ask( int i ) { int cnt = 0; for( ;i;i -= i & -i ) cnt += t[i]; return cnt; }
}

void solve( int l, int r, int ql, int qr ) {
    if( ql > qr ) return;
    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 cntl = 0, cntr = 0;
    for( int i = ql;i <= qr;i ++ )
        if( q[i].op == 2 ) {
            int cnt = BIT :: ask( q[i].y ) - BIT :: ask( q[i].x - 1 );
            if( q[i].k <= cnt ) L[++ cntl] = q[i];
            else q[i].k -= cnt, R[++ cntr] = q[i];
        }
        else {
            if( q[i].x <= mid ) BIT :: add( q[i].k, q[i].y ), L[++ cntl] = q[i];
            else R[++ cntr] = q[i];
        }
    for( int i = ql;i <= qr;i ++ )
        if( q[i].op == 1 and q[i].x <= mid )
            BIT :: add( q[i].k, -q[i].y );
    for( int i = 1;i <= cntl;i ++ ) q[i + ql - 1] = L[i];
    for( int i = 1;i <= cntr;i ++ ) q[i + ql + cntl - 1] = R[i];
    solve( l, mid, ql, ql + cntl - 1 );
    solve( mid + 1, r, ql + cntl, qr );
}

int main() {
    int cnt = 0, Max = 0;
    scanf( "%d %d", &n, &m );
    for( int i = 1;i <= n;i ++ ) {
        scanf( "%d", &a[i] );
        q[++ cnt] = (node){ 1, a[i], 1, i, cnt };
        Max = max( Max, a[i] );
    }
    char op[10]; int x, y, k;
    for( int i = 1;i <= m;i ++ ) {
        scanf( "%s", op );
        if( op[0] == 'Q' ) {
            scanf( "%d %d %d", &x, &y, &k );
            q[++ cnt] = (node) { 2, x, y, k, cnt };
        }
        else {
            scanf( "%d %d", &x, &y );
            q[++ cnt] = (node){ 1, a[x], -1, x, cnt };
            a[x] = y;
            q[++ cnt] = (node){ 1, a[x], 1, x, cnt };
            Max = max( Max, y );
        }
    }
    solve( 0, Max, 1, cnt );
    sort( q + 1, q + cnt + 1, []( node x, node y ) { return x.id < y.id; } );
    for( int i = 1;i <= cnt;i ++ )
        if( q[i].op == 2 ) printf( "%d\n", ans[q[i].id] );
    return 0;
}

[ZJOI2013]K大数查询

[ZJOI2013]K大数查询

注意这里是 k k k 大数,不是 k k k 小数。

那么二分答案时候,把 > m i d >mid >mid 的挂上去即可。

或者你整体取相反数后做 k k k 小数,最后输出答案再取反回来也行。

#include <bits/stdc++.h>
using namespace std;
#define maxn 50005
#define int long long
struct node { int op, x, y, k, id; }q[maxn], L[maxn], R[maxn];
int n, m;
int ans[maxn];

namespace SGT {
    struct node { int cnt, tag; }t[maxn << 2];
    #define lson now << 1
    #define rson now << 1 | 1
    #define mid  (l + r >> 1)
    void pushdown( int now, int l, int r ) {
        if( ! t[now].tag ) return;
        t[lson].tag += t[now].tag;
        t[lson].cnt += t[now].tag * (mid - l + 1);
        t[rson].tag += t[now].tag;
        t[rson].cnt += t[now].tag * (r - mid);
        t[now].tag = 0;
    }
    void modify( int now, int l, int r, int L, int R, int x ) {
        if( R < l or r < L ) return;
        if( L <= l and r <= R ) { t[now].tag += x; t[now].cnt += x * (r - l + 1); return; }
        pushdown( now, l, r );
        modify( lson, l, mid, L, R, x );
        modify( rson, mid + 1, r, L, R, x );
        t[now].cnt = t[lson].cnt + t[rson].cnt;
    }
    int query( int now, int l, int r, int L, int R ) {
        if( R < l or r < L ) return 0;
        if( L <= l and r <= R ) return t[now].cnt;
        pushdown( now, l, r );
        return query( lson, l, mid, L, R ) + query( rson, mid + 1, r, L, R );
    }
    #undef lson
    #undef rson
    #undef mid
}

void solve( int l, int r, int ql, int qr ) {
    if( ql > qr ) return;
    if( l == r ) { for( int i = ql;i <= qr;i ++ ) ans[q[i].id] = l; return; }
    int mid = l + r >> 1;
    int cntl = 0, cntr = 0;
    for( int i = ql;i <= qr;i ++ )
        if( q[i].op == 1 ) {
            if( q[i].k > mid ) SGT :: modify( 1, 1, n, q[i].x, q[i].y, 1 ), R[++ cntr] = q[i];
            else L[++ cntl] = q[i];
        }
        else {
            int cnt = SGT :: query( 1, 1, n, q[i].x, q[i].y );
            if( q[i].k <= cnt ) R[++ cntr] = q[i];
            else q[i].k -= cnt, L[++ cntl] = q[i];
        }
    for( int i = 1;i <= cntr;i ++ )
        if( R[i].op == 1 )
            SGT :: modify( 1, 1, n, R[i].x, R[i].y, -1 );
    for( int i = 1;i <= cntl;i ++ ) q[ql + i - 1] = L[i];
    for( int i = 1;i <= cntr;i ++ ) q[ql + i + cntl - 1] = R[i];
    solve( mid + 1, r, ql + cntl, qr );
    solve( l, mid, ql, ql + cntl - 1 );
}

signed main() {
    scanf( "%lld %lld", &n, &m );
    for( int i = 1, op, x, y, k;i <= m;i ++ ) {
        scanf( "%lld %lld %lld %lld", &op, &x, &y, &k );
        q[i] = (node){ op, x, y, k, i };
    }
    solve( -n, n, 1, m );
    sort( q + 1, q + m + 1, []( node x, node y ) { return x.id < y.id; } );
    for( int i = 1;i <= m;i ++ ) if( q[i].op == 2 ) printf( "%lld\n", ans[q[i].id] );
    return 0;
}

[国家集训队]矩阵乘法

[国家集训队]矩阵乘法

数据结构用二维树状数组即可。

#include <bits/stdc++.h>
using namespace std;
#define maxn 505
#define maxm 400005
struct node { int op, l1, r1, l2, r2, k, id; }q[maxm], L[maxm], R[maxm];
int n, m;
int ans[maxm];

namespace BIT {
    int t[maxn][maxn];
    void add( int x, int y, int v ) {
        for( int i = x;i <= n;i += i & -i )
            for( int j = y;j <= n;j += j & -j )
                t[i][j] += v;
    }
    int ask( int x, int y ) {
        int cnt = 0;
        for( int i = x;i;i -= i & -i )
            for( int j = y;j;j -= j & -j )
                cnt += t[i][j];
        return cnt;
    }
}

void solve( int l, int r, int ql, int qr ) {
    if( qr < ql ) return;
    if( l == r ) { for( int i = ql;i <= qr;i ++ ) ans[q[i].id] = l; return; }
    int mid = l + r >> 1;
    int cntl = 0, cntr = 0;
    for( int i = ql;i <= qr;i ++ )
        if( q[i].op == 1 ) {
            if( q[i].k <= mid ) BIT :: add( q[i].l1, q[i].r1, 1 ), L[++ cntl] = q[i];
            else R[++ cntr] = q[i];
        }
        else {
            int cnt = BIT :: ask( q[i].l2, q[i].r2 ) - BIT :: ask( q[i].l1 - 1, q[i].r2 ) - BIT :: ask( q[i].l2, q[i].r1 - 1 ) + BIT :: ask( q[i].l1 - 1, q[i].r1 - 1 );
            if( q[i].k <= cnt ) L[++ cntl] = q[i];
            else q[i].k -= cnt, R[++ cntr] = q[i];
        }
    for( int i = 1;i <= cntl;i ++ ) if( L[i].op == 1 ) BIT :: add( L[i].l1, L[i].r1, -1 );
    for( int i = 1;i <= cntl;i ++ ) q[ql + i - 1] = L[i];
    for( int i = 1;i <= cntr;i ++ ) q[ql + cntl + i - 1] = R[i];
    solve( l, mid, ql, ql + cntl - 1 );
    solve( mid + 1, r, ql + cntl, qr );
}

int main() {
    scanf( "%d %d", &n, &m );
    int cnt = 0;
    for( int i = 1;i <= n;i ++ )
        for( int j = 1, x;j <= n;j ++ ) {
            scanf( "%d", &x );
            q[++ cnt] = (node){ 1, i, j, i, j, x, cnt };
        }
    for( int i = 1, l1, r1, l2, r2, k;i <= m;i ++ ) {
        scanf( "%d %d %d %d %d", &l1, &r1, &l2, &r2, &k );
        q[++ cnt] = (node){ 2, l1, r1, l2, r2, k, cnt };
    }
    solve( 0, 1e9, 1, cnt );
    sort( q + 1, q + cnt + 1, []( node x, node y ) { return x.id < y.id; } );
    for( int i = 1;i <= cnt;i ++ ) if( q[i].op == 2 ) printf( "%d\n", ans[q[i].id] );
    return 0;
}

[THUPC2017] 天天爱射击

[THUPC2017] 天天爱射击

直接从子弹入手并不好做,这里稍微绕个弯,当我们知道所有木板最后一次被哪颗子弹射中,就能统计出每个子弹射穿的木板数。

所以应当以木板为因变量。考虑每次二分最后被射击的子弹 m i d mid mid,然后 check \text{check} check m i d mid mid 个子弹能否射穿该木板。

发现这个可以整体二分做。

这里的 [ l , r ] [l,r] [l,r] 不再是权值,而是二分的子弹编号。

我们这里依然把子弹和木板放在同一个操作数组里面,子弹为类型 1 1 1,木板为类型 2 2 2

注意两种操作的时间顺序安排,我们应该是先把所有子弹打了过后,再检查该木板被射中的次数是否达到承载值。

单点修改,区间查询,树状数组可做。

如果次数到达承载值,证明可能在更早时刻就已经被射穿,放入左边,否则放入右边,递归分治。

#include <bits/stdc++.h>
using namespace std;
#define maxn 400005
struct node { int op, x, y, k, id; }q[maxn], L[maxn], R[maxn];
int n, m;
int ans[maxn];

namespace BIT {
    int t[maxn];
    void add( int i, int x ) { for( ;i < maxn;i += i & -i ) t[i] += x; }
    int ask( int i ) { int cnt = 0; for( ;i;i -= i & -i ) cnt += t[i]; return cnt; }
}

void solve( int l, int r, int ql, int qr ) {
    if( ql > qr ) return;
    if( l == r ) { for( int i = ql;i <= qr;i ++ ) if( q[i].op == 2 ) ans[l] ++; return; }
    int mid = l + r >> 1;
    int cntl = 0, cntr = 0;
    for( int i = ql;i <= qr;i ++ )
        if( q[i].op == 1 ) {
            if( q[i].k <= mid ) BIT :: add( q[i].x, 1 ), L[++ cntl] = q[i];
            else R[++ cntr] = q[i];
        }
        else {
            int cnt = BIT :: ask( q[i].y ) - BIT :: ask( q[i].x - 1 );
            if( q[i].k <= cnt ) L[++ cntl] = q[i];
            else q[i].k -= cnt, R[++ cntr] = q[i];
        }
    for( int i = 1;i <= cntl;i ++ ) if( L[i].op == 1 ) BIT :: add( L[i].x, -1 );
    for( int i = 1;i <= cntl;i ++ ) q[ql + i - 1] = L[i];
    for( int i = 1;i <= cntr;i ++ ) q[ql + cntl + i - 1] = R[i];
    solve( l, mid, ql, ql + cntl - 1 );
    solve( mid + 1, r, ql + cntl, qr );
}

int main() {
    scanf( "%d %d", &n, &m );
    for( int i = 1, x, y, k;i <= n;i ++ ) {
        scanf( "%d %d %d", &x, &y, &k );
        q[i + m] = (node){ 2, x, y, k, i + m };
    }
    for( int i = 1, x;i <= m;i ++ ) {
        scanf( "%d", &x );
        q[i] = (node){ 1, x, 0, i, i };
    }
    solve( 1, m + 1, 1, n + m );
    for( int i = 1;i <= m;i ++ ) printf( "%d\n", ans[i] );
    return 0;
}

[CTSC2018]混合果汁

[CTSC2018]混合果汁

显然我们可以整体二分最低的美味度 m i d mid mid

然后把所有美味度 ≥ m i d \ge mid mid 的果汁都放入数据结构中。

但果汁有钱数和购买上限。

在二分后有个显然的贪心:越便宜的越先买。

我们可以用权值线段树,以单价为下标,然后记录总个数和总支付钱数。

查询时先看左区间,再看右区间。查询买最便宜的 l l l 个的钱数是否在小朋友承受范围内。

但这里我们发现,好像这里的贡献提前去掉和前面几个模板题目不太一样。因为当 m i d mid mid 移动时可能会加入更便宜的果汁,所以我们不能在现在就把某些小朋友的钱数减去一部分。

那怎么办?——我们直接不减,就只是把小朋友不断分组即可。

每次撤销就类似莫队,用一个指针移动。仔细想想(可见下面代码)每一种果汁最多上线段树两次。

总时间复杂度是可以保证的。

本题我将果汁和小朋友两种操作分开存的,可能更直白一点。

这里的顺序不再是时间顺序,而是果汁美味度大小的顺序。

#include <bits/stdc++.h>
using namespace std;
#define int long long
#define maxn 100005
struct juice { int d, p, l; }g[maxn];
struct child { int g, l, id; }q[maxn], L[maxn], R[maxn];
int n, m, now;
int ans[maxn];

namespace SGT {
    struct node { int cnt, sum; }t[maxn << 2];
    #define lson now << 1
    #define rson now << 1 | 1
    #define mid  (l + r >> 1)
    void modify( int p, int x, int now = 1, int l = 1, int r = 1e5 ) {
        if( l == r ) { t[now].cnt += x; t[now].sum += x * p; return; }
        if( p <= mid ) modify( p, x, lson, l, mid );
        else modify( p, x, rson, mid + 1, r );
        t[now].cnt = t[lson].cnt + t[rson].cnt;
        t[now].sum = t[lson].sum + t[rson].sum;
    }
    int query( int x, int now = 1, int l = 1, int r = 1e5 ) {
        if( ! x ) return 0;
        if( l == r ) return l * x;
        if( x <= t[lson].cnt ) return query( x, lson, l, mid );
        else return t[lson].sum + query( x - t[lson].cnt, rson, mid + 1, r );
    }
    #undef lson
    #undef rson
    #undef mid
}

void solve( int l, int r, int ql, int qr ) {
    if( ql > qr ) return;
    if( l == r ) { for( int i = ql;i <= qr;i ++ ) ans[q[i].id] = g[l].d; return; }
    int mid = l + r >> 1;
    while( now < mid ) ++ now, SGT :: modify( g[now].p, g[now].l );
    while( mid < now ) SGT :: modify( g[now].p, -g[now].l ), now --;
    int cntl = 0, cntr = 0;
    for( int i = ql;i <= qr;i ++ ) {
        if( SGT :: t[1].cnt < q[i].l ) R[++ cntr] = q[i];
        else if( SGT :: query( q[i].l ) <= q[i].g ) L[++ cntl] = q[i];
        else R[++ cntr] = q[i];
    }
    for( int i = 1;i <= cntl;i ++ ) q[ql + i - 1] = L[i];
    for( int i = 1;i <= cntr;i ++ ) q[ql + cntl + i - 1] = R[i];
    solve( l, mid, ql, ql + cntl - 1 );
    solve( mid + 1, r, ql + cntl, qr );
}

signed main() {
    scanf( "%lld %lld", &n, &m );
    for( int i = 1;i <= n;i ++ ) scanf( "%lld %lld %lld", &g[i].d, &g[i].p, &g[i].l );
    for( int i = 1;i <= m;i ++ ) scanf( "%lld %lld", &q[i].g, &q[i].l ), q[i].id = i;
    sort( g + 1, g + n + 1, []( juice x, juice y ) { return x.d > y.d; } );
    g[++ n] = { -1, 0, (int)1e18 };
    solve( 1, n, 1, m );
    for( int i = 1;i <= m;i ++ ) printf( "%lld\n", ans[i] );
    return 0;
}
  • 1
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值