整体二分 第k大数字
整体二分,在蓝书上被称为基于值域的整体分治算法
我更喜欢叫他整体二分(因为我感觉这样叫更牛逼一点)
发现了分治有一个必须满足的性质
如果我要把一个区间问题分治,那么这个被分治的区间的前后应该要么是单方面贡献,要么是结果根本不相关,也就是前面的对后面的没有影响
其实这个总结非常非常不严谨,估计除了我自己没有人能够理解我到底在说什么
因为我说的这两个分治根本就是不同的问题,前面的是CDQ分治,这个是明显是包含修改的。只不过,很多的时候修改被理解为带时间戳的数据而已,CDQ分治根本上能解决的还是简单的多维偏序。反正我目前看到的CDQ分治的题目都是这样的。
而后面说的就是整体二分,是不带修改的。整体二分的整体,说的就是这个分治其实是把这整个问题都分开了。
在整体二分的过程中,整个问题是真正得被分成了两个问题,两个完全独立的问题。
这个写一题就能理解了
K-th Number
Time Limit: 20000MS | Memory Limit: 65536K | |
---|---|---|
Total Submissions: 86706 | Accepted: 31344 | |
Case Time Limit: 2000MS |
Description
You are working for Macrohard company in data structures department. After failing your previous task about key insertion you were asked to write a new data structure that would be able to return quickly k-th order statistics in the array segment.
That is, given an array a[1...n] of different integer numbers, your program must answer a series of questions Q(i, j, k) in the form: "What would be the k-th number in a[i...j] segment, if this segment was sorted?"
For example, consider the array a = (1, 5, 2, 6, 3, 7, 4). Let the question be Q(2, 5, 3). The segment a[2...5] is (5, 2, 6, 3). If we sort this segment, we get (2, 3, 5, 6), the third number is 5, and therefore the answer to the question is 5.
Input
The first line of the input file contains n --- the size of the array, and m --- the number of questions to answer (1 <= n <= 100 000, 1 <= m <= 5 000).
The second line contains n different integer numbers not exceeding 109 by their absolute values --- the array for which the answers should be given.
The following m lines contain question descriptions, each description consists of three numbers: i, j, and k (1 <= i <= j <= n, 1 <= k <= j - i + 1) and represents the question Q(i, j, k).
Output
For each question output the answer to it --- the k-th number in sorted a[i...j] segment.
Sample Input
7 3
1 5 2 6 3 7 4
2 5 3
4 4 1
1 7 3
Sample Output
5
6
3
Hint
This problem has huge input,so please use c-style input(scanf,printf),or you may got time limit exceed.
Source
Northeastern Europe 2004, Northern Subregion
给定一个序列,再给若干个询问,每个询问查询一个给定的区间的第k大值
我们考虑对值域二分答案。假设数值a的值域是\([Mina,Maxa]\),第一次二分的值是\(mid=(Mina+Maxa)>>1\)
每次二分,遍历所有的询问\([l_i,r_i]\),对每一个询问统计这个区间里面小于mid的值有多少,记为\(c[i]\)。
我们可以把询问分为两类
1.\(c[i]\leq k[i]\)的
那么说明这个询问的答案在区间\([Mina,mid]\)之间。
2.$c[i]>k$的
那么说明这个询问的答案在区间$[mid+1,Maxa]$之间。
既然我们可以把询问分为这样两类,那不是把数组a也可以分?
按照mid的大小,把a分为两部分,第一个部分是\(a[i]\leq mid\)的
第二个部分是\(a[i]>mid\)的
很明显,第一个部分的询问的答案就在第一个部分的数组中,而第二个部分的询问的答案就在第二个部分的数组中
这样,这就完全的变成了两个问题了,我们不仅二分答案,还二分了数组和询问。所以叫做整体二分
代码不难写,我觉得比CDQ分治好写多了
#include <iostream>
#include <stdio.h>
#include <algorithm>
#include <cmath>
#include <math.h>
#include <string.h>
#define ll long long
using namespace std;
inline int read() {
char c=getchar();int a=0,b=1;
for(;c<'0'||c>'9';c=getchar())if(c=='-')b=-1;
for(;c>='0'&&c<='9';c=getchar())a=a*10+c-48;return a*b;
}
struct node
{
int opt,x,y,z;
}q[200001],lq[100001],rq[100001];
int n,m,tot,tr[200001],ans[100001];
const int INF=1e9;
inline int lowbit(int x)
{
return x&(-x);
}
int ask(int i)
{
int ans=0;
while(i>0)
{
ans+=tr[i];
i-=lowbit(i);
}
return ans;
}
void add(int i,int x)
{
while(i<=n)
{
tr[i]+=x;
i+=lowbit(i);
}
}
void solve(int lval,int rval,int st,int ed)
{
if(st>ed)return ;
if(lval==rval)
{
for(int i=st;i<=ed;i++)
{
if(q[i].opt!=0)ans[q[i].opt]=lval;
}
return;
}
int mid=(lval+rval)>>1;
int lt=0,rt=0;
for(int i=st;i<=ed;i++)
{
if(q[i].opt==0)
{
if(q[i].y<=mid)add(q[i].x,1),lq[++lt]=q[i];
else rq[++rt]=q[i];
}
else
{
int cnt=ask(q[i].y)-ask(q[i].x-1);
if(cnt>=q[i].z)lq[++lt]=q[i];
else q[i].z-=cnt,rq[++rt]=q[i];
}
}
for(int i=ed;i>=st;i--)
{
if(q[i].opt==0&&q[i].y<=mid)add(q[i].x,-1);
}
for(int i=1;i<=lt;i++)
q[st+i-1]=lq[i];
for(int i=1;i<=rt;i++)
q[st+i+lt-1]=rq[i];
solve(lval,mid,st,st+lt-1);
solve(mid+1,rval,st+lt,ed);
}
int main()
{
// freopen(".in","r",stdin);
// freopen(".out","w",stdout);
n=read(),m=read();
for(int i=1;i<=n;i++)
{
q[++tot].opt=0;
q[tot].x=i;q[tot].y=read();
}
for(int i=1;i<=m;i++)
{
int l=read(),r=read(),k=read();
q[++tot].opt=i;q[tot].x=l,q[tot].y=r,q[tot].z=k;
}
solve(-INF,INF,1,tot);
for(int i=1;i<=m;i++)
{
cout<<ans[i]<<endl;
}
return 0;
}
这个代码是完全按照蓝书上写的
这个把最开始给的数组当作修改的操作好些是比分开来写更好写一些
学到了
然后就是加强版,如果在中间插入修改操作呢?
Dynamic Rankings
题目描述
给定一个含有 \(n\) 个数的序列 \(a_1,a_2 \dots a_n\),需要支持两种操作:
Q l r k
表示查询下标在区间 \([l,r]\) 中的第 \(k\) 小的数C x y
表示将 \(a_x\) 改为 \(y\)
输入格式
第一行两个正整数 \(n,m\),表示序列长度与操作个数。
第二行 \(n\) 个整数,表示 \(a_1,a_2 \dots a_n\)。
接下来 \(m\) 行,每行表示一个操作,都为上述两种中的一个。
输出格式
对于每一次询问,输出一行一个整数表示答案。
样例 #1
样例输入 #1
5 3
3 2 1 4 7
Q 1 4 3
C 2 6
Q 2 5 3
样例输出 #1
3
6
提示
【数据范围】
对于 \(10\%\) 的数据,\(1\le n,m \le 100\);
对于 \(20\%\) 的数据,\(1\le n,m \le 1000\);
对于 \(50\%\) 的数据,\(1\le n,m \le 10^4\);
对于 \(100\%\) 的数据,\(1\le n,m \le 10^5\),\(1 \le l \le r \le n\),\(1 \le k \le r-l+1\),\(1\le x \le n\),\(0 \le a_i,y \le 10^9\)。
请注意常数优化,但写法正常的整体二分和树套树都可以以大约 \(1000\text{ms}\) 每个点的时间通过。
来源:bzoj1901
本题数据为洛谷自造数据,使用CYaRon耗时5分钟完成数据制作。
这。。好像代码不用改啊?
上面的代码是不是已经可以支持修改了?
第一个循环的顺序就是时间顺序啊,那统计的答案也是这个顺序吧?
只是因为操作是修改,所以要记录每次修改前是什么数值
这个玩意改完就没有东西了,但是不愧是我,还是调了一个小时
#include<bits/stdc++.h>
#define ll long long
using namespace std;
inline int read() {
char c=getchar();int a=0,b=1;
for(;c<'0'||c>'9';c=getchar())if(c=='-')b=-1;
for(;c>='0'&&c<='9';c=getchar())a=a*10+c-48;return a*b;
}
struct node
{
int opt,x,y,z,pos;
}q[400001],lq[400001],rq[400001];
int n,m,tot,tr[400001],ans[400001],a[400001],tmp[400001],turn[400001];
const int INF=1e9;
inline int lowbit(int x)
{
return x&(-x);
}
int ask(int i)
{
int ans=0;
while(i>0)
{
ans+=tr[i];
i-=lowbit(i);
}
return ans;
}
void add(int i,int x)
{
while(i<=n)
{
tr[i]+=x;
i+=lowbit(i);
}
}
void solve(int lval,int rval,int st,int ed)
{
if(st>ed)return;
if(lval==rval)
{
for(int i=st;i<=ed;i++)
{
if(q[i].opt==0)ans[q[i].pos]=lval;
}
return;
}
int mid=(lval+rval)>>1;
int lt=0,rt=0;
for(int i=st;i<=ed;i++)
{
if(q[i].opt!=0)
{
if(q[i].y<=mid)add(q[i].x,q[i].opt),lq[++lt]=q[i];
else rq[++rt]=q[i];
}
else
{
int cnt=ask(q[i].y)-ask(q[i].x-1);
if(cnt>=q[i].z)lq[++lt]=q[i];
else q[i].z-=cnt,rq[++rt]=q[i];
}
}
for(int i=ed;i>=st;i--)
{
if(q[i].opt!=0&&q[i].y<=mid)add(q[i].x,-q[i].opt);
}
for(int i=1;i<=lt;i++)q[st+i-1]=lq[i];
for(int i=1;i<=rt;i++)q[st+i+lt-1]=rq[i];
solve(lval,mid,st,st+lt-1);
solve(mid+1,rval,st+lt,ed);
}
int main()
{
// freopen(".in","r",stdin);
// freopen(".out","w",stdout);
n=read(),m=read();
int al=0;
for(int i=1;i<=n;i++)
{
q[++tot].opt=1;
q[tot].x=i;q[tot].y=a[i]=read();
tmp[++al]=a[i];
}
for(int i=1;i<=m;i++)
{
char c;
cin>>c;
if(c=='Q')
{
int l=read(),r=read(),k=read();
q[++tot].opt=0;
q[tot].x=l;
q[tot].y=r;
q[tot].z=k;
q[tot].pos=i;
}
else
{
int x=read(),y=read();
q[++tot].opt=-1;
q[tot].x=x;
q[tot].y=a[x];
q[++tot].opt=1;
q[tot].x=x;
q[tot].y=y;
a[x]=y;
tmp[++al]=a[x];
}
}
map<int,int> ma;int tot1=0;
sort(tmp+1,tmp+1+al);
for(int i=1;i<=al;i++)
{
if(ma[tmp[i]]==0)ma[tmp[i]]=++tot1;
}
for(int i=1;i<=tot;i++)
if(q[i].opt!=0)
turn[ma[q[i].y]]=q[i].y,q[i].y=ma[q[i].y];
solve(1,tot1,1,tot);
for(int i=1;i<=m;i++)
{
if(ans[i]!=0)
{
// cout<<ans[i]<<' ';
cout<<turn[ans[i]]<<endl;
}
}
return 0;
}