题目:

BZOJ3110

分析:

整体二分模板题……

先明确一下题意:每个位置可以存放多个数,第一种操作是“加入 (insert) ”一个数而不是“加上 (add) ”一个数。

首先考虑只有一次询问的情况。设询问的名次为\(k\),我们二分出一个答案\(mid\),然后遍历所有修改。建立一棵区间线段树(下标是位置的线段树),对于一个给\([a,b]\)区间加入一个数\(c\)的修改,如果\(c\geq mid\),就给\([a,b]\)这个区间整体加\(1\)。最后查询询问区间的和,即这个区间中不小于\(mid\)的数的数量。如果这个数量大于等于\(k\)则向上二分,并记录答案,否则向下二分。这样单次询问的复杂度是\(O(mlog_2^2n)\)。

可以看出,询问时最耗时间的是遍历所有修改并维护线段树。而这个操作只与当前二分到的\(mid\)有关,与询问无关。因此考虑把所有询问放在一个集合中,每次把询问分为将要“向上二分”(答案在\([mid+1,r]\))和“向下二分”(答案在\([l,mid]\)两个集合,并把可能对它们产生贡献的修改也分别加入这两个集合,然后分别递归下去。这就是“整体二分”。下面重点介绍如何对询问和修改分为两个集合。以下把修改和询问统称为“操作”。

询问的分法比较显然。如同只有一个询问的情况,把当前操作集合中的修改全部插入到线段树上。如果询问区间的和大于等于询问的名次则把这个询问分到“向上二分”的集合中,否则分到“向下二分”的集合中。

考虑修改。如果一个修改所加入的数不大于\(mid\),那么对于已经认定答案在\([mid+1,r]\)的询问一定是没有贡献的,所以只需要加到向下二分的集合中;如果一个修改所加入的数大于\(mid\),那么对于已经认定答案在\([l,mid]\)的询问一定是有\(1\)的贡献。如果把答案在\([l,mid]\)的询问所求的名次都减去\(1\),则这个修改也只会对向上二分的集合中的询问有贡献,只需要加到向上二分的集合中。这样每次都把所有操作分成独立的两部分,最多分\(log_2n\)次。每层所有操作集合的并集刚好是原集合,所以每层的时间复杂度是\(nlog_2n\),总复杂度\(O(nlog_2^2n)\)。具体流程可以参考代码。

代码:

并不需要真正把操作分成两个集合,只分它们的编号即可。

#include <cstdio>
#include <algorithm>
#include <cctype>
#include <cstring>
using namespace std; namespace zyt
{
template<typename T>
inline void read(T &x)
{
char c;
bool f = false;
x = 0;
do
c = getchar();
while (c != '-' && !isdigit(c));
if (c == '-')
f = true, c = getchar();
do
x = x * 10 + c - '0', c = getchar();
while (isdigit(c));
if (f)
x = -x;
}
template<typename T>
inline void write(T x)
{
static char buf[20];
char *pos = buf;
if (x < 0)
putchar('-'), x = -x;
do
*pos++ = x % 10 + '0';
while (x /= 10);
while (pos > buf)
putchar(*--pos);
}
typedef long long ll;
const int N = 5e4 + 10, B = 16, CHANGE = 1, QUERY = 2;
int n, m, id[N], ans[N];
struct node
{
int type, l, r;
ll c;
}opt[N];
namespace Segment_Tree
{
struct node
{
ll sum, tag;
}tree[1 << (B + 1)];
inline void update(const int rot)
{
tree[rot].sum = tree[rot << 1].sum + tree[rot << 1 | 1].sum;
}
inline void push_down(const int rot, const int lt, const int rt)
{
if (tree[rot].tag)
{
ll &tag = tree[rot].tag;
int mid = (lt + rt) >> 1;
tree[rot << 1].sum += tag * (mid - lt + 1);
tree[rot << 1].tag += tag;
tree[rot << 1 | 1].sum += tag * (rt - mid);
tree[rot << 1 | 1].tag += tag;
tag = 0;
}
}
void add(const int rot, const int lt, const int rt, const int ls, const int rs, const int x)
{
if (ls <= lt && rt <= rs)
{
tree[rot].sum += x * (rt - lt + 1), tree[rot].tag += x;
return;
}
int mid = (lt + rt) >> 1;
push_down(rot, lt, rt);
if (ls <= mid)
add(rot << 1, lt, mid, ls, rs, x);
if (rs > mid)
add(rot << 1 | 1, mid + 1, rt, ls, rs, x);
update(rot);
}
ll query(const int rot, const int lt, const int rt, const int ls, const int rs)
{
if (ls <= lt && rt <= rs)
return tree[rot].sum;
int mid = (lt + rt) >> 1;
ll ans = 0;
push_down(rot, lt, rt);
if (ls <= mid)
ans += query(rot << 1, lt, mid, ls, rs);
if (rs > mid)
ans += query(rot << 1 | 1, mid + 1, rt, ls, rs);
return ans;
}
}
void solve(const int idl, const int idr, const int l, const int r)
{//As for any question, determine
//wheather there are more than opt[i].c numbers greater than mid or not
//If so, ans[i] will be greater than mid. Otherwise less.
using Segment_Tree::query;
using Segment_Tree::add;
static int newl[N], newr[N];
if (l == r)
{
for (int i = idl; i <= idr; i++)
if (opt[id[i]].type == QUERY)
ans[id[i]] = l;
return;
}
int lcnt = 0, rcnt = 0;
int mid = (l + r) >> 1;
for (int i = idl; i <= idr; i++)
{
if (opt[id[i]].type == CHANGE)
{
if (opt[id[i]].c <= mid)
newl[lcnt++] = id[i];
else
{
add(1, 1, n, opt[id[i]].l, opt[id[i]].r, 1);
query(1, 1, n, 100, 100);
newr[rcnt++] = id[i];
}
}
else
{
ll tmp = query(1, 1, n, opt[id[i]].l, opt[id[i]].r);
if (tmp < opt[id[i]].c)
newl[lcnt++] = id[i], opt[id[i]].c -= tmp;
else
newr[rcnt++] = id[i];
}
}
for (int i = idl; i <= idr; i++)
if (opt[id[i]].type == CHANGE && opt[id[i]].c > mid)
add(1, 1, n, opt[id[i]].l, opt[id[i]].r, -1);
memcpy(id + idl, newl, sizeof(int[lcnt]));
memcpy(id + idl + lcnt, newr, sizeof(int[rcnt]));
if (lcnt)
solve(idl, idl + lcnt - 1, l, mid);
if (rcnt)
solve(idl + lcnt, idr, mid + 1, r);
}
int work()
{
read(n), read(m);
for (int i = 1; i <= m; i++)
{
read(opt[i].type);
read(opt[i].l);
read(opt[i].r);
read(opt[i].c);
id[i] = i;
}
solve(1, m, -N, N);
for (int i = 1; i <= m; i++)
if (opt[i].type == QUERY)
write(ans[i]), putchar('\n');
return 0;
}
}
int main()
{
freopen("3110.in", "r", stdin);
freopen("3110.out", "w", stdout);
return zyt::work();
}

【BZOJ3110】[ZJOI2013]K大数查询(整体二分)的更多相关文章

  1. BZOJ3110:[ZJOI2013]K大数查询(整体二分)

    Description 有N个位置,M个操作.操作有两种,每次操作如果是1 a b c的形式表示在第a个位置到第b个位置,每个位置加入一个数c.如果是2 a b c形式,表示询问从第a个位置到第b个位 ...

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

    3110: [Zjoi2013]K大数查询 Time Limit: 20 Sec  Memory Limit: 512 MBSubmit: 6265  Solved: 2060[Submit][Sta ...

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

    题目描述 有N个位置,M个操作.操作有两种,每次操作如果是1 a b c的形式表示在第a个位置到第b个位置,每个位置加入一个数c.如果是2 a b c形式,表示询问从第a个位置到第b个位置,第C大的数 ...

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

    终于入门整体二分了,勉勉强强算是搞懂了一个题目吧. 整体二分很多时候可以比较好的离线处理区间\(K\)大值的相关问题.考虑算法流程: 操作队列\(arr\),其中有询问和修改两类操作. 每次在答案的可 ...

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

    有N个位置,M个操作.操作有两种,每次操作如果是1 a b c的形式表示在第a个位置到第b个位置,每个位置加入一个数c如果是2 a b c形式,表示询问从第a个位置到第b个位置,第C大的数是多少. N ...

  6. BZOJ.3110.[ZJOI2013]K大数查询(整体二分 树状数组/线段树)

    题目链接 BZOJ 洛谷 整体二分求的是第K小(利用树状数组).求第K大可以转为求第\(n-K+1\)小,但是这样好像得求一个\(n\). 注意到所有数的绝对值\(\leq N\),将所有数的大小关系 ...

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

    题目描述 有N个位置,M个操作.操作有两种,每次操作如果是: 1 a b c:表示在第a个位置到第b个位置,每个位置加上一个数c 2 a b c:表示询问从第a个位置到第b个位置,第C大的数是多少. ...

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

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

  9. BZOJ3110:[ZJOI2013]K大数查询(整体二分版)

    浅谈离线分治算法:https://www.cnblogs.com/AKMer/p/10415556.html 题目传送门:https://lydsy.com/JudgeOnline/problem.p ...

  10. BZOJ 3110 [ZJOI2013]K大数查询 (整体二分+线段树)

    和dynamic rankings这道题的思想一样 只不过是把树状数组换成线段树区间修改,求第$K$大的而不是第$K$小的 这道题还有负数,需要离散 #include <vector> # ...

随机推荐

  1. radis入门

    redis介绍 是远程的,有客户端.服务端 存内存,吃内存 应用场景 缓存 队列 list操作 push pop 数据存储[根据redis硬盘持久化的机制,这里不展开] 5种数据类型 string 字 ...

  2. Tornado进阶

    三.Tornado进阶 3.1 Application settings debug,设置tornado是否工作在调试模式,默认为False即工作在生产模式.当设置debug=True 后,torna ...

  3. python爬虫28 | 你爬下的数据不分析一波可就亏了啊,使用python进行数据可视化

    通过这段时间 小帅b教你从抓包开始 到数据爬取 到数据解析 再到数据存储 相信你已经能抓取大部分你想爬取的网站数据了 恭喜恭喜 但是 数据抓取下来 要好好分析一波 最好的方式就是把数据进行可视化 这样 ...

  4. js之字典操作

    <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8&quo ...

  5. 03 Python的那些事

    目录: 1) 创始人以及重要发展历程 2) Python语言的特点 3) TIOBE排名 4) 解释器 5) Python后缀名 6) 变量规则和约定 7) 常量 8) 注释 9) 缩进 10) Py ...

  6. BZOJ 2095 [POI2010]Bridges (最大流、欧拉回路)

    洛谷上有这题,但是输出方案缺SPJ..(而且我也懒得输出方案了) 题目链接: https://www.lydsy.com/JudgeOnline/problem.php?id=2095 题解: 首先判 ...

  7. Linear and Logistic Regression in TensorFlow

    Linear and Logistic Regression in TensorFlow Graphs and sessions TF Ops: constants, variables, funct ...

  8. [bzoj1001]狼爪兔子[平面图的最小割等于其对偶图的最短路]

    一定要仔细算内存,,,又少写一个零.. #include <bits/stdc++.h> using namespace std; template<const int _n,con ...

  9. Luogu P1257 平面上的最接近点对 暴力

    这道题数据不大 两点距离用勾股定理求 #include<iostream> #include<cmath> using namespace std; struct node{ ...

  10. 【BZOJ4514】数字配对(费用流)

    题意: 有 n 种数字,第 i 种数字是 ai.有 bi 个,权值是 ci. 若两个数字 ai.aj 满足,ai 是 aj 的倍数,且 ai/aj 是一个质数, 那么这两个数字可以配对,并获得 ci× ...