#edu2005. 基于值域的整体分治算法(整体二分)

基于值域的整体分治算法(整体二分)

例1: 静态区间第k小/P3834可持久化线段树

参考代码:

//整体二分
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N=2e5+10;
int n,m;
struct node{
    int x,y,z,op;
}que[N*2],lq[N*2],rq[N*2];
int cnt,tr[N],ans[N];
void add(int x,int d)
{
    for(int i=x;i<=n;i+=i&(-i))tr[i]+=d;
}
int query(int x)
{
    int ret=0;
    for(int i=x;i;i-=i&(-i))ret+=tr[i];
    return ret;
}
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(que[i].op>0)ans[que[i].op]=lval;
        return;
    }
    int mid=lval+rval>>1;
    int lt=0,rt=0;
    for(int i=st;i<=ed;i++)
        if(que[i].op==0)   //赋值操作
        {
            if(que[i].y<=mid)add(que[i].x,1),lq[++lt]=que[i];
            else rq[++rt]=que[i];
        }
        else  //询问
        {
            int num=query(que[i].y)-query(que[i].x-1);
            if(num>=que[i].z)lq[++lt]=que[i];
            else que[i].z-=num,rq[++rt]=que[i];
        }
    for(int i=ed;i>=st;i--)//还原
        if(que[i].op==0 && que[i].y<=mid)add(que[i].x,-1);
    
    for(int i=1;i<=lt;i++)que[st+i-1]=lq[i];
    for(int i=1;i<=rt;i++)que[st+lt+i-1]=rq[i];
    
    solve(lval,mid,st,st+lt-1);
    solve(mid+1,rval,st+lt,ed);
}
int main()
{
    ios::sync_with_stdio(false); cin.tie(0);
    cin>>n>>m;
    for(int i=1;i<=n;i++)
    {
        int val;
        cin>>val;
        que[++cnt].op=0;   //表示赋值 0表示增加一个值
        que[cnt].x=i;        //位置
        que[cnt].y=val;      //值
    }
    for(int i=1;i<=m;i++)
    {
        int l,r,k; cin>>l>>r>>k;
        que[++cnt].x=l;  que[cnt].y=r; //[x,y]区间
        que[cnt].z=k;                  //第k大
        que[cnt].op=i;                 //对应询问编号
    }
    solve(0,1e9,1,cnt);   //值域二分 
    for(int i=1;i<=m;i++)cout<<ans[i]<<"\n";  
    
    return 0;
}

例2. 动态区间第k小[P2617 Dynamic Rankings]

参考代码:

//整体二分
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N=2e5+10;
int n,m,a[N];
struct node{
    int x,y,z,op;
}que[N*3],lq[N*3],rq[N*3];
int cnt,tr[N],ans[N];
bool flag[N];
void add(int x,int d)
{
    for(int i=x;i<=n;i+=i&(-i))tr[i]+=d;
}
int query(int x)
{
    int ret=0;
    for(int i=x;i;i-=i&(-i))ret+=tr[i];
    return ret;
}
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(que[i].op>0)ans[que[i].op]=lval;
        return;
    }
    int mid=lval+rval>>1;
    int lt=0,rt=0;
    for(int i=st;i<=ed;i++)
        if(que[i].op==0)   //赋值操作
        {
            if(que[i].y<=mid)add(que[i].x,1),lq[++lt]=que[i];
            else rq[++rt]=que[i];
        }
        else if(que[i].op==-1)
        {
            if(que[i].y<=mid)add(que[i].x,-1),lq[++lt]=que[i];
            else rq[++rt]=que[i];
        }
        else  //询问
        {
            int num=query(que[i].y)-query(que[i].x-1);
            if(num>=que[i].z)lq[++lt]=que[i];
            else que[i].z-=num,rq[++rt]=que[i];
        }
    for(int i=ed;i>=st;i--)//还原
        if(que[i].op==0 && que[i].y<=mid)add(que[i].x,-1);
        else if(que[i].op==-1 && que[i].y<=mid)add(que[i].x,1);
    
    for(int i=1;i<=lt;i++)que[st+i-1]=lq[i];
    for(int i=1;i<=rt;i++)que[st+lt+i-1]=rq[i];
    
    solve(lval,mid,st,st+lt-1);
    solve(mid+1,rval,st+lt,ed);
}
int main()
{
    ios::sync_with_stdio(false); cin.tie(0);
    cin>>n>>m;
    for(int i=1;i<=n;i++)
    {
        cin>>a[i];
        que[++cnt].op=0;   //表示赋值 0表示增加一个值
        que[cnt].x=i;        //位置
        que[cnt].y=a[i];      //值
    }
    for(int i=1;i<=m;i++)
    {
        char op;
        cin>>op;
        if(op=='Q')
        {
            int l,r,k; cin>>l>>r>>k;
            que[++cnt].x=l;  que[cnt].y=r; //[x,y]区间
            que[cnt].z=k;                  //第k大
            que[cnt].op=i;                 //对应询问编号
            flag[i]=1;
        }else
        {
            int x,y;
            cin>>x>>y;
            //去掉一个值A[x]
            que[++cnt].op=-1;
            que[cnt].x=x;
            que[cnt].y=a[x];
            //增加一个值A[x]+=y
            que[++cnt].op=0;
            que[cnt].x=x;
            que[cnt].y=y;
            a[x]=y;
                        
        }        
    }
    solve(0,1e9,1,cnt);   //值域二分 
    for(int i=1;i<=m;i++)
        if(flag[i])
            cout<<ans[i]<<"\n";  
    return 0;
}

例3. 静态矩阵第k小 P1527 [国家集训队] 矩阵乘法

参考代码:

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N=505,Q=6e4+10;
int n,q,ans[Q],cnt,t[N][N];
struct Node{
    int x,y,val;
    bool operator<(Node a)const{
        return val<a.val;
    }
}a[N*N];
struct Query{
    int x1,y1,x2,y2,k,id;
}que[Q],lq[Q],rq[Q];

void add(int x,int y,int d)
{
    for(int i=x;i<=n;i+=i&(-i))
        for(int j=y;j<=n;j+=j&(-j))
            t[i][j]+=d;
}

int query(int x,int y)
{
    int ret=0;
    for(int i=x;i;i-=i&(-i))
        for(int j=y;j;j-=j&(-j))ret+=t[i][j];
    return ret;
}
int cal(int x1,int y1,int x2,int y2)
{
    return query(x2,y2)-query(x1-1,y2)-query(x2,y1-1)+query(x1-1,y1-1);
}


void solve(int l,int r,int st,int ed)
{
    if(st>ed)return;
    if(l==r)
    {
        for(int i=st;i<=ed;i++)ans[que[i].id]=a[l].val;
        return;
    }
    int mid=l+r>>1; 
    for(int i=l;i<=mid;i++)add(a[i].x,a[i].y,1);   //小于 a[mid].val ,树状数组上增加1
    
    int lt=0,rt=0;
    for(int i=st;i<=ed;i++)
    {
        int num=cal(que[i].x1,que[i].y1,que[i].x2,que[i].y2);
        
        if(num>=que[i].k)lq[++lt]=que[i];
        else que[i].k-=num,rq[++rt]=que[i];
    }
    //还原
    for(int i=l;i<=mid;i++)add(a[i].x,a[i].y,-1);

    for(int i=1;i<=lt;i++)que[st+i-1]=lq[i];
    for(int i=1;i<=rt;i++)que[st+lt+i-1]=rq[i];
    
    solve(l,mid,st,st+lt-1);
    solve(mid+1,r,st+lt,ed); 
}
int main()
{
    ios::sync_with_stdio(false); cin.tie(0);
    cin>>n>>q;
    for(int i=1;i<=n;i++)
        for(int j=1;j<=n;j++)
        {
            ++cnt;
            cin>>a[cnt].val;
            a[cnt].x=i,a[cnt].y=j;    
        }
    for(int i=1;i<=q;i++)
    {
        cin>>que[i].x1>>que[i].y1>>que[i].x2>>que[i].y2>>que[i].k;
        que[i].id=i;
    }
    sort(a+1,a+cnt+1);

    solve(1,cnt,1,q);
    for(int i=1;i<=q;i++)cout<<ans[i]<<"\n";   
    
    return 0;
}

例4 P3332 K大数查询

使用线段树做区间修改,然后再整体二分

#include <bits/stdc++.h>
#define int long long
using namespace std;
typedef long long ll;
const int N=5e4+10;
int n,q;
struct Node{
    ll l,r,c;
    int op,id;
}que[N],lq[N],rq[N];
ll t[N*4],ans[N];  //线段树
ll lazy[N*4];
bool flag[N];
void pushdown(int p,int l,int r)
{
    if(lazy[p])
    {
        int mid=l+r>>1;
        lazy[p<<1]+=lazy[p]; lazy[p<<1|1]+=lazy[p];
        t[p<<1]+=lazy[p]*(mid-l+1);  t[p<<1|1]+=lazy[p]*(r-mid); 
        lazy[p]=0;
    }
}
void modify(int p,int l,int r,int ql,int qr,ll d)
{
    if(ql<=l && r<=qr)
    {
       t[p]+=d*(r-l+1);
       lazy[p]+=d;
       return;
    }
    pushdown(p,l,r);
    int mid=l+r>>1;
    if(ql<=mid)modify(p<<1,l,mid,ql,qr,d);
    if(mid<qr)modify(p<<1|1,mid+1,r,ql,qr,d);
    
    t[p]=t[p<<1]+t[p<<1|1];
}
int query(int p,int l,int r,int ql,int qr)
{
    if(ql<=l && r<=qr)
    {
        return t[p];
    }
    pushdown(p,l,r);
    int mid=l+r>>1;
    int ret=0;
    if(ql<=mid)ret+=query(p<<1,l,mid,ql,qr);
    if(mid<qr)ret+=query(p<<1|1,mid+1,r,ql,qr);
    return ret;
}

void solve(ll lval,ll rval,int st,int ed)
{
    if(st>ed)return;
    if(lval==rval){
        for(int i=st;i<=ed;i++)
            if(que[i].op==2)//查询
                ans[que[i].id]=lval;
        return;
    }
    ll mid=lval+rval>>1;
    int lt=0,rt=0;
    for(int i=st;i<=ed;i++)
    {
        if(que[i].op==1)//插入操作
        {
            if(que[i].c>mid)modify(1,1,n,que[i].l,que[i].r,1),rq[++rt]=que[i];
            else lq[++lt]=que[i];
        }
        else{  //查询
            int num=query(1,1,n,que[i].l,que[i].r);
            if(num>=que[i].c)rq[++rt]=que[i];
            else que[i].c-=num,lq[++lt]=que[i];
        }
    }
    //还原
    for(int i=ed;i>=st;i--)
        if(que[i].op==1 && que[i].c>mid)modify(1,1,n,que[i].l,que[i].r,-1);

    for(int i=1;i<=lt;i++)que[st+i-1]=lq[i];
    for(int i=1;i<=rt;i++)que[st+lt+i-1]=rq[i];

    solve(lval,mid,st,st+lt-1);
    solve(mid+1,rval,st+lt,ed);
}

signed main()
{
    ios::sync_with_stdio(false); cin.tie(0);
    cin>>n>>q;
    for(int i=1;i<=q;i++)
    {
        int op,l,r,c;
        cin>>que[i].op>>que[i].l>>que[i].r>>que[i].c;
        if(que[i].op==2)flag[i]=1;
        que[i].id=i;               //查询
    }
    solve(1,1ll<<63,1,q);

    for(int i=1;i<=q;i++)
        if(flag[i])cout<<ans[i]<<"\n";    
    
    return 0;
}

学习完毕

{{ select(1) }}

  • YES
  • NO