题目:

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. Yii 时间日期组件与composer 下载中出现的问题

    首先本篇主要讲3点 一个Yii时间日期组件的两种用法 笔者使用composer下载该组件时出现问题的解决办法 1.composer下载出现的问题 file could not be downloade ...

  2. 08.C语言:特殊函数

    C语言:特殊函数 1.递归函数: 与普通函数比较,执行过程不同,该函数内部调用它自己,它的执行必须要经过两个阶段:递推阶段,回归阶段: 当不满足回归条件,不再递推: #include <stdi ...

  3. C. Day at the Beach

    codeforces 599c C. Day at the Beach One day Squidward, Spongebob and Patrick decided to go to the be ...

  4. PAT 1146 Topological Order

    This is a problem given in the Graduate Entrance Exam in 2018: Which of the following is NOT a topol ...

  5. 【Codeforces 922D】Robot Vacuum Cleaner

    [链接] 我是链接,点我呀:) [题意] 让你把n个字符串重新排序,然后按顺序连接在一起 使得这个组成的字符串的"sh"子序列最多 [题解] /* * 假设A的情况好于B * 也就 ...

  6. BNUOJ 1206 A Plug for UNIX

    A Plug for UNIX Time Limit: 1000ms Memory Limit: 65536KB This problem will be judged on PKU. Origina ...

  7. 模拟退火算fa

    转载:http://www.cnblogs.com/heaad/archive/2010/12/20/1911614.html 优化算法入门系列文章目录(更新中): 1. 模拟退火算法 2. 遗传算法 ...

  8. vue.js通讯----父亲拿儿子的数据

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

  9. I - 最少拦截系统

    #include<stdio.h> #include<string.h> #include<algorithm> using namespace std; ],su ...

  10. CentOS redis安装配置

    编译依赖安装 yum install gcc-c++ yum install -y tcl 安装步骤 下载:wget http://download.redis.io/releases/redis-5 ...