整体二分
整体二分
区间第 \(k\) 小问题 一组询问
给定长度为 \(n\) \((n \leq 2 \times 10 ^ 5)\) 的数组 \(a_i\) 。有一组询问,求 \([l,r]\) 区间内第 \(k\) 小的数。
这个题有非常多种做法:
-
直接对这个区间排序然后取出第 \(k\) 个数即可,复杂度 \(O(n \log n)\)。
-
二分答案,设当前二分到的区间是 \([L,R]\) , 设 \(mid = \frac{L+R}{2}\) ,计算 \([l,r]\) 中有一个 \(\leq mid\) 的数,时间复杂度 \(O(n \log V)\) 。
区间第 \(k\) 小问题 多组询问
问题
给定长度为 \(n\) \((n \leq 2 \times 10 ^ 5)\) 的数组 \(a_i\) 。有 \(q\) \((q \leq 2 \times 10 ^5)\) 组询问,求 \([l,r]\) 区间内第 \(k\) 小的数。
解法
这个题可以用主席树,但是空间复杂度较高,这里介绍整体二分做法。
我们考虑上一个题的第二个做法,对每一组询问二分,但这样时间复杂度是 \(O(nq \log n)\) 的。
我们考虑优化,发现有很多组询问的 \([L,R]\) 是相同的。
比如第一次二分的区间都是 \([1,V]\) ,第二次就会变成 \([1,\frac{V}{2}]\) 和 \([\frac{V}{2} +1,V]\) 。
我们可以把二分的区间相同的询问一起处理,用一个数据结构维护。
这样就会有一个类似树状的结构。
每一次我们把小于 \(mid\) 的数丢到左侧,大于 \(mid\) 的数丢到右侧。
这样复杂度就会将至 \(O((n+q)\log ^2 n)\) 。
代码
#include<bits/stdc++.h>
using namespace std;
const int N=2e5+100;
int a[N],mp[N],n,ans[N],t[N],m;
struct Num{int x,p;}num[N];
struct ask{int l,r,k,id;}q[N];
int lowbit(int x){return x&(-x);}
void update(int x,int k){for(int i=x;i<=n;i+=lowbit(i))t[i]+=k;}
int query(int x){int ans=0;for(int i=x;i;i-=lowbit(i))ans+=t[i];return ans;}
void dfs(int l,int r,int numl,int numr,int askl,int askr){
if(l>r)return;
if(l==r){
for(int i=askl;i<=askr;i++)ans[q[i].id]=l;
return;
}
int mid=(l+r)/2,nnuml=numl-1,naskl=askl-1;
for(int i=numl;i<=numr;i++){
if(num[i].x<=mid){
update(num[i].p,1);
swap(num[++nnuml],num[i]);//把小于mid的数丢到左侧,大于mid的就在右侧了
}
}
for(int i=askl;i<=askr;i++){
int p=query(q[i].r)-query(q[i].l-1);
if(p>=q[i].k){
swap(q[++naskl],q[i]);//把小于mid的询问丢到左侧,大于mid的询问就在右侧
}else q[i].k-=p;
}
for(int i=numl;i<=nnuml;i++)update(num[i].p,-1);
dfs(l,mid,numl,nnuml,askl,naskl);
dfs(mid+1,r,nnuml+1,numr,naskl+1,askr);
}
int main(){
scanf("%d %d",&n,&m);
for(int i=1;i<=n;i++)scanf("%d",&a[i]);
for(int i=1;i<=n;i++)mp[i]=a[i];
sort(mp+1,mp+n+1);
int nn=unique(mp+1,mp+n+1)-mp-1;
for(int i=1;i<=n;i++)a[i]=lower_bound(mp+1,mp+nn+1,a[i])-mp;
for(int i=1;i<=n;i++)num[i]={a[i],i};
for(int i=1,l,r,k;i<=m;i++){
scanf("%d %d %d",&l,&r,&k);
q[i]={l,r,k,i};
}
dfs(1,nn,1,n,1,m);
for(int i=1;i<=m;i++)printf("%d\n",mp[ans[i]]);
}
整体二分的一般思路
整体二分需要结合分治思想。
-
计算分类到 \([l,r]\) 的修改对询问的影响。
-
不带影响递归 \([l,mid]\) 。
-
带影响递归 \([mid+1,r]\) 。
例题
P3527 [POI 2011] MET-Meteors
题意
有一个长度为 \(m\) 的环形数列,第 \(i\) 个位置属于第 \(o_i\) 个国家。
有 \(k\) 次事件,每次会将一个区间内的数加上一个正整数。
每一个国家有一个期望值 \(p_i\) ,求每一个在数列上的位置的数的和在第几次事件后不小于 \(p_i\) 。
其中,\(1 \leq n,m,k \leq 3 \times 10 ^ 5\) 。
解法
二分时间即可,和区间第 \(k\) 小几乎一样,复杂度 \(O(n \log ^ 2 n)\) 。
代码
#include<bits/stdc++.h>
typedef unsigned long long ll;
using namespace std;
const int N=3e5+100,mod=1e9+7;
struct ask{
vector<int>pos;
int id;ll p;
}q[N];
struct change{
int l,r,t;ll a;
}ch[N*2];
ll t[N];
int n,m,k,ans[N],cnt;
int lowbit(int x){return x&(-x);}
void update(int x,ll k){if(x==0)return;for(int i=x;i<=m;i+=lowbit(i))t[i]+=k;}
ll query(int x){ll ans=0;for(int i=x;i;i-=lowbit(i))ans+=t[i];return ans;}
void dfs(int l,int r,int L,int R,int cl,int cr){
if(l==r){
for(int i=L;i<=R;i++)ans[q[i].id]=l;
return;
}
int nL=L-1,ncl=cl-1,mid=(l+r)/2;
for(int i=cl;i<=cr;i++){
if(ch[i].t<=mid){
update(ch[i].l,ch[i].a);
update(ch[i].r+1,-ch[i].a);
swap(ch[++ncl],ch[i]);
}
}
for(int i=L;i<=R;i++){
ll p=0;
for(int j:q[i].pos)p+=query(j);
if(p>=q[i].p){
swap(q[++nL],q[i]);
}else q[i].p-=p;
}
for(int i=cl;i<=ncl;i++){
update(ch[i].l,-ch[i].a);
update(ch[i].r+1,ch[i].a);
}
sort(ch+ncl+1,ch+cr+1,[](change a,change b){return a.t<b.t;});
dfs(l,mid,L,nL,cl,ncl);dfs(mid+1,r,nL+1,R,ncl+1,cr);
}
int main(){
scanf("%d %d",&n,&m);
for(int i=1,x;i<=m;i++){
scanf("%d",&x);
q[x].pos.push_back(i);
}
for(int i=1,x;i<=n;i++){
scanf("%d",&x);
q[i].p=x;
q[i].id=i;
}
scanf("%d",&k);
for(int i=1,l,r,a;i<=k;i++){
scanf("%d %d %d",&l,&r,&a);
if(l<=r)ch[++cnt]={l,r,i,a};
else{
ch[++cnt]={1,r,i,a};
ch[++cnt]={l,m,i,a};
}
}
dfs(1,k+1,1,n,1,cnt);
for(int i=1;i<=n;i++){
if(ans[i]==k+1){
printf("NIE\n");
}else printf("%d\n",ans[i]);
}
}
P1527 [国家集训队] 矩阵乘法
题意
有一个 \(n \times n\) 的矩阵,有 \(q\) 次询问,求一个子矩阵中第 \(k\) 小的数。
其中, \(1 \leq n \leq 500\) , \(1 \leq q \leq 6 \times 10 ^ 4\) 。
解法
没什么好说的,二分答案,用二维树状数组维护即可,复杂度 \(O((n ^ 2 + q)\log ^3 n)\) 。
代码
#include<bits/stdc++.h>
typedef long long ll;
using namespace std;
const int N=510,M=6e4+100;
int n,m,t[N][N],ans[M],cnt,tmp[N*N];
struct Num{
int x,y,a;
}a[N*N];
struct Ask{
int x1,y1,x2,y2,id,k;
}q[M];
inline int lowbit(int x){return x&(-x);}
void update(int x,int y,int k){
for(int i=x;i<=n;i+=lowbit(i)){
for(int j=y;j<=n;j+=lowbit(j)){
t[i][j]+=k;
}
}
}
int query(int x,int y){
int res=0;
for(int i=x;i;i-=lowbit(i)){
for(int j=y;j;j-=lowbit(j)){
res+=t[i][j];
}
}
return res;
}
void dfs(int l,int r,int L,int R,int nl,int nr){
if(l==r){
for(int i=L;i<=R;i++)ans[q[i].id]=l;
return;
}
int mid=(l+r)/2,nL=L-1,nnl=nl-1;
for(int i=nl;i<=nr;i++){
if(a[i].a<=mid){
update(a[i].x,a[i].y,1);
swap(a[++nnl],a[i]);
}
}
for(int i=L;i<=R;i++){
int p=query(q[i].x2,q[i].y2)-query(q[i].x2,q[i].y1-1)-query(q[i].x1-1,q[i].y2)+query(q[i].x1-1,q[i].y1-1);
if(p>=q[i].k){
swap(q[++nL],q[i]);
}else q[i].k-=p;
}
for(int i=nl;i<=nnl;i++){
if(a[i].a<=mid)update(a[i].x,a[i].y,-1);
}
dfs(l,mid,L,nL,nl,nnl);dfs(mid+1,r,nL+1,R,nnl+1,nr);
}
int main(){
scanf("%d %d",&n,&m);
for(int i=1,x;i<=n;i++){
for(int j=1;j<=n;j++){
scanf("%d",&x);
a[++cnt]={i,j,x};
tmp[cnt]=x;
}
}
sort(tmp+1,tmp+cnt+1);
int nn=unique(tmp+1,tmp+cnt+1)-tmp-1;
for(int i=1;i<=n*n;i++)a[i].a=lower_bound(tmp+1,tmp+nn+1,a[i].a)-tmp;
for(int i=1;i<=m;i++){
scanf("%d %d %d %d %d",&q[i].x1,&q[i].y1,&q[i].x2,&q[i].y2,&q[i].k);
q[i].id=i;
}
dfs(1,n*n,1,m,1,n*n);
for(int i=1;i<=m;i++)printf("%d\n",tmp[ans[i]]);
}
P2617 Dynamic Rankings
题意
给定一个含有 \(n\) 个数的序列 \(a_1,a_2 \dots a_n\),需要支持两种操作共 \(m\) 次:
Q l r k表示查询下标在区间 \([l,r]\) 中的第 \(k\) 小的数;C x y表示将 \(a_x\) 改为 \(y\)。
其中, \(1 \leq n,m \leq 1 \times 10 ^ 5\) 。
解法
依旧二分答案,把修改和查询一起二分,注意按时间排序,复杂度 \(O((n + m) \log ^ 2 (n + m))\) 。
代码
#include<bits/stdc++.h>
typedef long long ll;
using namespace std;
const int N=1e5+100;
struct ask{
int op,x,y,id,k,t;
}q[N*3];
int n,m,Q,mp[N*2],cnt,nn,ans[N],t[N],a[N];
int lowbit(int x){return x&(-x);}
void update(int x,int k){for(int i=x;i<=n;i+=lowbit(i))t[i]+=k;}
int query(int x){int ans=0;for(int i=x;i;i-=lowbit(i))ans+=t[i];return ans;}
void dfs(int l,int r,int L,int R){
if(l==r){
for(int i=L;i<=R;i++)ans[q[i].id]=l;
return;
}
int mid=(l+r)/2,nL=L-1,p;
for(int i=L;i<=R;i++){
if(q[i].op==0){
p=query(q[i].y)-query(q[i].x-1);
if(p>=q[i].k)swap(q[++nL],q[i]);
else q[i].k-=p;
}else if(q[i].op==1){
if(q[i].y<=mid){
update(q[i].x,-1);
swap(q[++nL],q[i]);
}
}else{
if(q[i].y<=mid){
update(q[i].x,1);
swap(q[++nL],q[i]);
}
}
}
for(int i=L;i<=nL;i++){
if(q[i].op==2&&q[i].y<=mid)update(q[i].x,-1);
if(q[i].op==1&&q[i].y<=mid)update(q[i].x,1);
}
sort(q+nL+1,q+R+1,[](ask a,ask b){return a.t<b.t;});
dfs(l,mid,L,nL);dfs(mid+1,r,nL+1,R);
}
int main(){
scanf("%d %d",&n,&Q);
for(int i=1;i<=n;i++){
scanf("%d",&a[i]);
q[++m]={2,i,a[i],0,0,m};
mp[++nn]=a[i];
}
char op;
for(int i=1,l,r,c;i<=Q;i++){
scanf(" %c %d %d",&op,&l,&r);
if(op=='Q'){
scanf("%d",&c);
q[++m]={0,l,r,++cnt,c,m};
}else{
mp[++nn]=r;
q[++m]={1,l,a[l],0,0,m};
q[++m]={2,l,r,0,0,m};
a[l]=r;
}
}
sort(mp+1,mp+nn+1);
nn=unique(mp+1,mp+nn+1)-mp-1;
for(int i=1;i<=m;i++){
if(q[i].op==1||q[i].op==2){
q[i].y=lower_bound(mp+1,mp+nn+1,q[i].y)-mp;
}
}
dfs(1,nn,1,m);
for(int i=1;i<=cnt;i++)printf("%d\n",mp[ans[i]]);
}
P3332 [ZJOI2013] K 大数查询
题意
你需要维护 \(n\) 个可重整数集,集合的编号从 \(1\) 到 \(n\)。
这些集合初始都是空集,有 \(m\) 个操作:
1 l r c:表示将 \(c\) 加入到编号在 \([l,r]\) 内的集合中2 l r c:表示查询编号在 \([l,r]\) 内的集合的并集中,第 \(c\) 大的数是多少。
注意可重集的并是不去除重复元素的,如 \(\{1,1,4\}\cup\{5,1,4\}=\{1,1,4,5,1,4\}\)。
其中, \(1 \leq n,m \leq 5 \times 10 ^ 4\) 。
解法
先离线,二分第 \(c\) 大的数,用线段树区间加和区间查询维护即可,复杂度 \(O(n \log ^ 2 n)\) 。
代码
#include<bits/stdc++.h>
#define mid ((l+r)>>1)
#define ls (rt<<1)
#define rs (rt<<1|1)
#define lson ls,l,mid
#define rson rs,mid+1,r
typedef long long ll;
using namespace std;
const int N=5e4+100;
struct tree{
ll cnt,tag;
}t[N*8];
struct oper{
int op,l,r,id,t;ll c;
}q[N];
ll ans[N];
int n,m,mx,cnt;
void pushtag(int rt,int l,int r,ll k){
t[rt].tag+=k;
t[rt].cnt+=(r-l+1)*k;
}
void pushdown(int rt,int l,int r){
if(t[rt].tag){
pushtag(lson,t[rt].tag);
pushtag(rson,t[rt].tag);
t[rt].tag=0;
}
}
void pushup(int rt,int l,int r){
t[rt].cnt=t[ls].cnt+t[rs].cnt;
}
void update(int rt,int l,int r,int L,int R,ll k){
if(L<=l&&R>=r){
pushtag(rt,l,r,k);
return;
}
pushdown(rt,l,r);
if(L<=mid)update(lson,L,R,k);
if(R>mid)update(rson,L,R,k);
pushup(rt,l,r);
}
ll query(int rt,int l,int r,int L,int R){
if(L<=l&&R>=r)return t[rt].cnt;
pushdown(rt,l,r);
if(L<=mid&&R>mid)return query(lson,L,R)+query(rson,L,R);
if(L<=mid)return query(lson,L,R);
return query(rson,L,R);
}
void dfs(int l,int r,int L,int R){
if(l==r){
for(int i=L;i<=R;i++)if(q[i].op==2)ans[q[i].id]=l;
return;
}
int nL=L-1;
for(int i=L;i<=R;i++){
if(q[i].op==1){
if(q[i].c>mid){
update(1,1,n,q[i].l,q[i].r,1);
swap(q[++nL],q[i]);
}
}else{
ll p=query(1,1,n,q[i].l,q[i].r);
if(p>=q[i].c){
swap(q[++nL],q[i]);
}else q[i].c-=p;
}
}
for(int i=L;i<=nL;i++){
if(q[i].op==1){
update(1,1,n,q[i].l,q[i].r,-1);
}
}
sort(q+nL+1,q+R+1,[](oper a,oper b){return a.t<b.t;});
dfs(mid+1,r,L,nL);dfs(l,mid,nL+1,R);
}
int main(){
scanf("%d %d",&n,&m);
for(int i=1,op,l,r;i<=m;i++){
ll c;
scanf("%d %d %d %lld",&op,&l,&r,&c);
if(op==2)cnt++;
q[i]={op,l,r,cnt,i,c};
}
dfs(-n,n,1,m);
for(int i=1;i<=cnt;i++)printf("%lld\n",ans[i]);
}

浙公网安备 33010602011771号