P3332 [ZJOI2013]K大数查询 整体二分

终于入门整体二分了,勉勉强强算是搞懂了一个题目吧。

整体二分很多时候可以比较好的离线处理区间\(K\)大值的相关问题。考虑算法流程:

操作队列\(arr\),其中有询问和修改两类操作。

每次在答案的可行值域上二分一个\(mid\),把询问的答案\(>mid\)的分在\(R\)部,\(<=mid\)的分在\(L\)部。把修改的值\(>mid\)的分在\(R\)部,\(<=mid\)的分在\(L\)部。

何谓整体二分?就是直接一起二分所有的询问操作的答案,然后暴力扫描当前操作区间,将其划分为答案的左子区间与右子区间两个部分。

那么以什么为划分依据呢?看看这个操作对于左子区间有没有贡献。如果没有,那么就划分到右子区间中,然后将这个操作的权值更改为这个贡献减去所需的贡献,反之,则划分到左子区间中,同时将这个操作的贡献加入某一个容器,为询问操作服务。

这么说可能有点晕。就这道题说的话,应该是这样:

我们设尚未解决的操作区间为\([ql,qr]\),答案区间为[l,r][l,r],令当前答案为\(mid\)。

则若该操作是添加操作,如果其添加的\(C<=mid\),这此次操作对于左子区间有贡献,加入左子区间中,并将区间线段树中的区间\([q[i].l,q[i].r]\)整体加\(1\).

反之,则将操作加入到右子区间中。

若该操作是询问操作,如果当前的\(mid\)在线段树中查询到的,比它大的数的个数\(query()>=q[i].k\),则证明该询问操作应该在右子区间内可以找到答案。反之,则将\(q[i].k-=query()\),减去此次查询的贡献,然后将询问操作添加到左子区间中。

#include <cstdio>
#include <cstring>
#include <iostream>
#include <algorithm>
using namespace std;

#define int long long
const int N = 50000 + 5;

struct Ask {
    int l, r, v, id, op;

    void read () {
        cin >> op >> l >> r >> v;
    } 

}q[N], tl[N], tr[N];

int ans[N], tag[N << 2], clr[N << 2], sum[N << 2];

int n, m, Q;

#define lc (p << 1)
#define rc (p << 1 | 1)
#define mid ((l + r) >> 1)

void pushdown (int p, int l, int r) {
    if (clr[p]) {
        clr[p] = 0;
        tag[lc] = tag[rc] = 0;
        sum[lc] = sum[rc] = 0;
        clr[lc] = 1, clr[rc] = 1;
    }
    if (tag[p]) {
        tag[lc] += tag[p];
        tag[rc] += tag[p];
        sum[lc] += tag[p] * (mid - l + 1);
        sum[rc] += tag[p] * (r - mid);
        tag[p] = 0;
    }
}   

void push_up (int p) {
    sum[p] = sum[lc] + sum[rc];
} 

void modify (int ql, int qr, int w, int p = 1, int l = 1, int r = n) {
    if (ql <= l && r <= qr){
        tag[p] += w;
        sum[p] += w * (r - l + 1);
        return;
    }
    pushdown (p, l, r);
    if (ql <= mid) modify (ql, qr, w, lc, l, mid);
    if (mid < qr) modify (ql, qr, w, rc, mid + 1, r);
    push_up (p);
}

int query (int ql, int qr, int p = 1, int l = 1, int r = n) {
    if (ql <= l && r <= qr) {
        return sum[p];
    }
    int ret = 0; pushdown(p,l,r);
    if (ql <= mid) ret += query (ql, qr, lc, l, mid);
    if (mid < qr) ret += query (ql, qr, rc, mid + 1, r);
    return ret;
}

void solve (int st, int en, int l, int r) {
    // [st, en] -> 处理操作的左右端点
    // [l, r] -> 对应值域
    if (l == r) {
        for (int i = st; i <= en; ++i) {
            if (q[i].op == 2) ans[q[i].id] = l; // 查询
        }
        return;
    }
    int L = 0, R = 0;
    clr[1] = 1; tag[1] = sum[1] = 0;
    for (int i = st; i <= en; ++i) {
        if (q[i].op == 1){
            if (q[i].v > mid) { // > mid 的操作对于答案 <= mid 的询问不会影响
                modify (q[i].l, q[i].r, 1);
                tr[++R] = q[i];
            } else {
                tl[++L] = q[i];
            }
        } else {
            int val = query (q[i].l, q[i].r);
            if (val < q[i].v){
                q[i].v -= val;
                tl[++L] = q[i]; // L部答案 <= mid
            }else{
                tr[++R] = q[i]; // R部答案 >  mid
            }
        }
    }
    for (int i = 1; i <= L; ++i) {
        q[st + i - 1] = tl[i];
    }
    for (int i = L + 1; i <= L + R; ++i) {
        q[st + i - 1] = tr[i - L];
    }
    solve (st, st + L - 1, l, mid);
    solve (st + L, en, mid + 1, r);
}

signed main () {
    cin >> n >> m;
    for (int i = 1; i <= m; ++i) {
        q[i].read ();
        if (q[i].op == 2) {
            q[i].id = ++Q;
        }
    }
    solve (1, m, -n, n);
    for (int i = 1; i <= Q; ++i) {
        cout << ans[i] << endl;
    }
}

原文地址:https://www.cnblogs.com/maomao9173/p/10913560.html

时间: 2024-11-13 07:17:17

P3332 [ZJOI2013]K大数查询 整体二分的相关文章

[ZJOI2013]K大数查询——整体二分

新科技:整体二分 它能解决的典型问题:带修改区间第\(k\)大 大概的做法是这样的:我们一次二分一个值\(mid\),然后依据操作的答案与\(mid\)的大小关系把操作分别划到两边,然后递归下去.也就是相当于二分的是所有询问的答案 感觉其实这个跟在权值线段树上二分一个效果,只是用离线的方式替代掉了那一层权值线段树而已 计算可得复杂度为\(O(nlog^2n)\)(由主定理,\(T(n)=2T(n/2)+O(nlogn)=O(nlog^2n)\)) 拿线段树或者树状数组维护都行 板子题是这一道K大

BZOJ 3110: [Zjoi2013]K大数查询 [整体二分]

有N个位置,M个操作.操作有两种,每次操作如果是1 a b c的形式表示在第a个位置到第b个位置,每个位置加入一个数c如果是2 a b c形式,表示询问从第a个位置到第b个位置,第C大的数是多少. N,M<=50000,N,M<=50000a<=b<=N1操作中abs(c)<=N2操作中c<=Maxlongint 之前用树套树抄过一次...然而我并不适合写那玩意儿... 加上时间序的整体二分 普通的整体二分先处理了所有$[l,mid]$的影响因子在计算询问的答案来分组

BZOJ 3110 [Zjoi2013]K大数查询 ——整体二分

[题目分析] 整体二分显而易见. 自己YY了一下用树状数组区间修改,区间查询的操作. 又因为一个字母调了一下午. 貌似树状数组并不需要清空,可以用一个指针来维护,可以少一个log 懒得写了. [代码] #include <cstdio> #include <cstring> #include <iostream> #include <algorithm> using namespace std; #define maxn 50005 #define inf

【bzoj3110】[Zjoi2013]K大数查询 整体二分+树状数组区间修改

题目描述 有N个位置,M个操作.操作有两种,每次操作如果是1 a b c的形式表示在第a个位置到第b个位置,每个位置加入一个数c.如果是2 a b c形式,表示询问从第a个位置到第b个位置,第C大的数是多少. 输入 第一行N,M接下来M行,每行形如1 a b c或2 a b c 输出 输出每个询问的结果 样例输入 2 5 1 1 2 1 1 1 2 2 2 1 1 2 2 1 1 1 2 1 2 3 样例输出 1 2 1 题解 整体二分+树状数组区间修改 当年naive的树套树题解 前两天由于要

洛谷 P3332 [ZJOI2013]K大数查询 || bzoj3110

用树套树就很麻烦,用整体二分就成了裸题.... 错误: 1.尝试线段树套平衡树,码农,而且n*log^3(n)慢慢卡反正我觉得卡不过去 2.线段树pushdown写错...加法tag对于区间和的更新应该要乘上区间长度的 1 #include<cstdio> 2 #include<algorithm> 3 #include<cstring> 4 using namespace std; 5 typedef long long LL; 6 struct Q 7 { 8 LL

【BZOJ-3110】K大数查询 整体二分 + 线段树

3110: [Zjoi2013]K大数查询 Time Limit: 20 Sec  Memory Limit: 512 MBSubmit: 6265  Solved: 2060[Submit][Status][Discuss] Description 有N个位置,M个操作.操作有两种,每次操作如果是1 a b c的形式表示在第a个位置到第b个位置,每个位置加入一个数c如果是2 a b c形式,表示询问从第a个位置到第b个位置,第C大的数是多少. Input 第一行N,M接下来M行,每行形如1 a

P3332 [ZJOI2013]K大数查询

\(\color{#0066ff}{ 题目描述 }\) 有N个位置,M个操作.操作有两种,每次操作如果是: 1 a b c:表示在第a个位置到第b个位置,每个位置加上一个数c 2 a b c:表示询问从第a个位置到第b个位置,第C大的数是多少. \(\color{#0066ff}{输入格式}\) 第一行N,M接下来M行,每行形如1 a b c或2 a b c \(\color{#0066ff}{输出格式}\) 输出每个询问的结果 \(\color{#0066ff}{输入样例}\) 2 5 1 1

BZOJ 3110: [Zjoi2013]K大数查询 [树套树]

3110: [Zjoi2013]K大数查询 Time Limit: 20 Sec  Memory Limit: 512 MBSubmit: 6050  Solved: 2007[Submit][Status][Discuss] Description 有N个位置,M个操作.操作有两种,每次操作如果是1 a b c的形式表示在第a个位置到第b个位置,每个位置加入一个数c如果是2 a b c形式,表示询问从第a个位置到第b个位置,第C大的数是多少. Input 第一行N,M接下来M行,每行形如1 a

3110: [Zjoi2013]K大数查询 树状数组套线段树

3110: [Zjoi2013]K大数查询 Time Limit: 20 Sec  Memory Limit: 512 MBSubmit: 1384  Solved: 629[Submit][Status] Description 有N个位置,M个操作.操作有两种,每次操作如果是1 a b c的形式表示在第a个位置到第b个位置,每个位置加入一个数c如果是2 a b c形式,表示询问从第a个位置到第b个位置,第C大的数是多少. Input 第一行N,M接下来M行,每行形如1 a b c或2 a b