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

新科技:整体二分

它能解决的典型问题:带修改区间第\(k\)大

大概的做法是这样的:我们一次二分一个值\(mid\),然后依据操作的答案与\(mid\)的大小关系把操作分别划到两边,然后递归下去。也就是相当于二分的是所有询问的答案

感觉其实这个跟在权值线段树上二分一个效果,只是用离线的方式替代掉了那一层权值线段树而已

计算可得复杂度为\(O(nlog^2n)\)(由主定理,\(T(n)=2T(n/2)+O(nlogn)=O(nlog^2n)\))

拿线段树或者树状数组维护都行

板子题是这一道K大数查询

直接上代码了,很好写,跟\(cdq\)分治写起来很像

#include <algorithm>
#include  <iostream>
#include   <cstdlib>
#include   <cstring>
#include    <cstdio>
#include    <string>
#include    <vector>
#include     <cmath>
#include     <ctime>
#include     <queue>
#include       <map>
#include       <set>

using namespace std;

#define ull unsigned long long
#define pii pair<int, int>
#define uint unsigned int
#define mii map<int, int>
#define lbd lower_bound
#define ubd upper_bound
#define INF 0x3f3f3f3f
#define IINF 0x3f3f3f3f3f3f3f3fLL
#define vi vector<int>
#define ll long long
#define mp make_pair
#define pb push_back
#define re register
#define il inline

#define N 100000
#define M 100000

int n, m, qtot, ans[M+5];

struct Query {
  int type;
  int a, b, id;
  ll c;
}q[M+5], tl[M+5], tr[M+5];

struct SegmentTree {
  ll sumv[4*N+5], addv[4*N+5];
  int lson(int o) { return (o<<1); }
  int rson(int o) { return (o<<1|1); }
  void init() {
    memset(sumv, 0, sizeof sumv);
    memset(addv, 0, sizeof addv);
  }
  void pushup(int o) {
    sumv[o] = sumv[lson(o)]+sumv[rson(o)];
  }
  void pushdown(int o, int l, int r) {
    if(addv[o]) {
      int mid = (l+r)>>1;
      sumv[lson(o)] += (mid-l+1)*addv[o], addv[lson(o)] += addv[o];
      sumv[rson(o)] += (r-mid)*addv[o], addv[rson(o)] += addv[o];
      addv[o] = 0;
    }
  }
  void add(int o, int l, int r, int L, int R, ll k) {
    if(L <= l && r <= R) {
      sumv[o] += (r-l+1)*k, addv[o] += k;
      return ;
    }
    pushdown(o, l, r);
    int mid = (l+r)>>1;
    if(L <= mid) add(lson(o), l, mid, L, R, k);
    if(R > mid) add(rson(o), mid+1, r, L, R, k);
    pushup(o);
  }
  ll query(int o, int l, int r, int L, int R) {
    if(L <= l && r <= R) return sumv[o];
    pushdown(o, l, r);
    int mid = (l+r)>>1;
    ll ret = 0;
    if(L <= mid) ret += query(lson(o), l, mid, L, R);
    if(R > mid) ret += query(rson(o), mid+1, r, L, R);
    return ret;
  }
}ST;

void solve(int l, int r, int L, int R) {
  if(l > r) return ;
  if(L == R) {
    for(int i = l; i <= r; ++i) if(q[i].type == 2) ans[q[i].id] = L;
    return ;
  }
  int mid = L+(R-L)/2;
  int tot1 = 0, tot2 = 0;
  for(int i = l; i <= r; ++i) {
    if(q[i].type == 1) {
      if(q[i].c > mid) ST.add(1, 1, n, q[i].a, q[i].b, 1), tr[++tot2] = q[i];
      else tl[++tot1] = q[i];
    }
    else {
      ll v = ST.query(1, 1, n, q[i].a, q[i].b);
      if(v >= q[i].c) tr[++tot2] = q[i];
      else q[i].c -= v, tl[++tot1] = q[i];
    }
  }
  for(int i = 1; i <= tot1; ++i) q[l+i-1] = tl[i];
  for(int i = 1; i <= tot2; ++i) {
    q[l+tot1+i-1] = tr[i];
    if(tr[i].type == 1) ST.add(1, 1, n, tr[i].a, tr[i].b, -1);
  }
  solve(l, l+tot1-1, L, mid), solve(l+tot1, r, mid+1, R);
}

int main() {
  scanf("%d%d", &n, &m);
  for(int i = 1; i <= m; ++i) {
    scanf("%d%d%d%lld", &q[i].type, &q[i].a, &q[i].b, &q[i].c);
    if(q[i].type == 2) q[i].id = ++qtot;
  }
  solve(1, m, -n, n);
  for(int i = 1; i <= qtot; ++i) printf("%d\n", ans[i]);
  return 0;
}

原文地址:https://www.cnblogs.com/dummyummy/p/10915434.html

时间: 2024-10-22 14:06:07

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

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

终于入门整体二分了,勉勉强强算是搞懂了一个题目吧. 整体二分很多时候可以比较好的离线处理区间\(K\)大值的相关问题.考虑算法流程: 操作队列\(arr\),其中有询问和修改两类操作. 每次在答案的可行值域上二分一个\(mid\),把询问的答案\(>mid\)的分在\(R\)部,\(<=mid\)的分在\(L\)部.把修改的值\(>mid\)的分在\(R\)部,\(<=mid\)的分在\(L\)部. 何谓整体二分?就是直接一起二分所有的询问操作的答案,然后暴力扫描当前操作区间,将其

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的树套树题解 前两天由于要

【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大数查询 || 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: [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

BZOJ 3110: [Zjoi2013]K大数查询( 树状数组套主席树 )

BIT+(可持久化)权值线段树, 用到了BIT的差分技巧. 时间复杂度O(Nlog^2(N)) ----------------------------------------------------------------------------------------- #include<cstdio> #include<cctype> #include<cstring> #include<algorithm> using namespace std;