影片連結: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]); }