C108 整體二分+樹狀陣列(區修+區查)P3332 [ZJOI2013] K大數查詢

董晓發表於2024-03-30

影片連結:C108 整體二分+樹狀陣列(區修+區查)P3332 [ZJOI2013] K大數查詢_嗶哩嗶哩_bilibili

參考:C82 樹狀陣列 區修+區查 P3372 線段樹1 - 董曉 - 部落格園 (cnblogs.com)

Luogu P3332 [ZJOI2013] K大數查詢

// 整體二分+樹狀陣列(區修+區查)O(n*logn*logn)
#include <iostream>
#include <cstring>
#include <algorithm>
using namespace std;

typedef long long LL;
const int N=50005;
int n,m,cnt,ans[N];
struct Q{
  //查詢: opt=2,[x,y]第k大,id編號
  //插入: opt=1,[x,y]加入k
  int opt,x,y,id; LL k;
}q[N],q1[N],q2[N];

struct BIT{ //樹狀陣列
  LL s[N];
  void add(int x,int v){ //點修
    for(int i=x;i<=n;i+=i&-i)s[i]+=v;
  }
  LL sum(int x){ //字首和
    LL t=0;
    for(int i=x;i;i-=i&-i)t+=s[i];
    return t;
  }
}a,b; //a:di的區間和, b:di*(i-1)的區間和

void solve(int l,int r,int L,int R){
  if(l>r)return;
  if(L==R){
    for(int i=l;i<=r;i++)ans[q[i].id]=L; return;
  }
  int mid=(L+R)>>1;    //二分值域
  int p1=0,p2=0;       //分流操作
  for(int i=l;i<=r;i++){ //列舉操作
    int x=q[i].x,y=q[i].y;
    if(q[i].opt==1){   //插入
      if(q[i].k>mid)   //若k值大,則維護貢獻
        a.add(x,1),a.add(y+1,-1),
        b.add(x,x-1),b.add(y+1,-y),
        q2[++p2]=q[i]; //分流到右邊
      else q1[++p1]=q[i];
    } 
    else{ //查詢
      LL s=a.sum(y)*y-b.sum(y)
         -(a.sum(x-1)*(x-1)-b.sum(x-1));
      if(s>=q[i].k)    //若s太多,則第k大在右邊
        q2[++p2]=q[i]; //分流到右邊
      else q[i].k-=s,q1[++p1]=q[i];
    }
  }
  for(int i=1;i<=p2;i++) //清空貢獻
    if(q2[i].opt==1){
      int x=q2[i].x,y=q2[i].y;
      a.add(x,-1),a.add(y+1,1),
      b.add(x,-x+1),b.add(y+1,y);
    }
  for(int i=1;i<=p1;i++) q[l+i-1]=q1[i];
  for(int i=1;i<=p2;i++) q[l+p1+i-1]=q2[i];
  solve(l,l+p1-1,L,mid);
  solve(l+p1,r,mid+1,R); //分治
}
int main(){
  scanf("%d%d",&n,&m);
  for(int i=1;i<=m;i++){ //操作資訊
    scanf("%d%d%d%lld",&q[i].opt,&q[i].x,&q[i].y,&q[i].k);
    if(q[i].opt==2) q[i].id=++cnt;
  }
  solve(1,m,-n,n); //整體二分
  for(int i=1;i<=cnt;i++)printf("%d\n",ans[i]);
}

// 整體二分+線段樹 O(n*logn*logn)
#include <iostream>
#include <cstring>
#include <algorithm>
using namespace std;

typedef long long LL;
#define lc u<<1
#define rc u<<1|1
const int N=50005;
int n,m,cnt,ans[N];
struct Q{
  //查詢: opt=2,[x,y]第k大,id編號
  //插入: opt=1,[x,y]加入k
  int opt,x,y,id; LL k;
}q[N],q1[N],q2[N];

struct Tree{ //線段樹
  int l,r;
  LL sum,tag; //區間和,懶標記
}tr[N<<2];

void pushup(int u){ //上傳資訊
  tr[u].sum=tr[lc].sum+tr[rc].sum;
}
void pushdown(int u){ //下傳資訊
  if(!tr[u].tag) return;
  int l=tr[u].l,r=tr[u].r,mid=(l+r)>>1;
  tr[lc].tag+=tr[u].tag;
  tr[rc].tag+=tr[u].tag;
  tr[lc].sum+=tr[u].tag*(mid-l+1);
  tr[rc].sum+=tr[u].tag*(r-mid);
  tr[u].tag=0;
}
void build(int u,int l,int r){ //建樹
  tr[u]={l,r,0,0};
  if(l==r) return;
  int mid=(l+r)>>1;
  build(lc,l,mid);
  build(rc,mid+1,r);
}
void change(int u,int x,int y,LL v){ //區修
  int l=tr[u].l,r=tr[u].r;
  if(x<=l&&r<=y){
    tr[u].tag+=v;
    tr[u].sum+=v*(r-l+1);
    return;
  }
  pushdown(u);
  int mid=(l+r)>>1;
  if(x<=mid) change(lc,x,y,v);
  if(mid<y) change(rc,x,y,v);
  pushup(u);
}
LL query(int u,int x,int y){ //區查
  int l=tr[u].l,r=tr[u].r;
  if(x<=l&&r<=y) return tr[u].sum;
  pushdown(u);
  int mid=(l+r)>>1;LL s=0;
  if(x<=mid) s+=query(lc,x,y);
  if(mid<y) s+=query(rc,x,y);
  return s;
}
void solve(int l,int r,int L,int R){
  if(l>r) return;
  if(L==R){
    for(int i=l;i<=r;i++)ans[q[i].id]=L;
    return;
  }
  int mid=(L+R)>>1;   //二分值域
  int p1=0,p2=0;      //分流操作
  for(int i=l;i<=r;i++){ //列舉操作
    if(q[i].opt==1){  //插入
      if(q[i].k>mid)  //若k值大,則維護貢獻
        change(1,q[i].x,q[i].y,1),
        q2[++p2]=q[i]; //分流到右邊
      else q1[++p1]=q[i];
    }
    else{ //查詢
      LL s=query(1,q[i].x,q[i].y);
      if(s>=q[i].k)    //若s太多,則第k大在右邊
        q2[++p2]=q[i]; //分流到右邊
      else q[i].k-=s,q1[++p1]=q[i];
    }
  }
  for(int i=1;i<=p2;i++) //清空貢獻
    if(q2[i].opt==1)change(1,q2[i].x,q2[i].y,-1); 
  for(int i=1;i<=p1;i++) q[l+i-1]=q1[i];
  for(int i=1;i<=p2;i++) q[l+p1+i-1]=q2[i];
  solve(l,l+p1-1,L,mid);
  solve(l+p1,r,mid+1,R); //分治
}
int main(){
  scanf("%d%d",&n,&m);
  build(1,1,n);
  for(int i=1;i<=m;i++){ //操作資訊
    scanf("%d%d%d%lld",&q[i].opt,&q[i].x,&q[i].y,&q[i].k);
    if(q[i].opt==2) q[i].id=++cnt;
  }
  solve(1,m,-n,n); //整體二分
  for(int i=1;i<=cnt;i++)printf("%d\n",ans[i]);
}

相關文章