整体二分
分析
- 对整体进行二分,自带一个\(lg\),通常需要用数据结构维护
- 每次将答案的区间均分,询问,插入依次到应该在的地方,由于最多均分\(lg\)次,所以每个操作最多做\(lg\)次
基操
区间第K大等等状态具有二分性
模板
将原数列看作插入,将修改看作删除+插入
用树状数组维护01,时间复杂度是\(O(nlg^2n)\)
#include<bits/stdc++.h>
using namespace std;
const int N=1e5+5,M=3e5+5;
int n,m,cnt,c[N],ans[N],a[N];
struct A{int fl,x,y,k,id; }q[M],q1[M],q2[M];
char s[100];
inline void add(int x,int k) {
for(;x<=n;x+=x&-x) c[x]+=k;
}
inline int ask(int x) {
int ret=0;
for(;x;x-=x&-x) ret+=c[x];
return ret;
}
void work(int ql,int qr,int L,int R) {
if(ql>qr) return;
if(L==R) {
for(int i=ql;i<=qr;i++) {
if(q[i].fl==1) ans[q[i].id]=L;
}
return;
}
int mid=L+R>>1,t1=0,t2=0;
for(int i=ql;i<=qr;i++) {
if(q[i].fl!=1) {
if(q[i].y<=mid) {
add(q[i].x,q[i].fl?-1:1);
q1[++t1]=q[i];
} else q2[++t2]=q[i];
} else {
int t=ask(q[i].y)-ask(q[i].x-1);
if(t>=q[i].k) q1[++t1]=q[i];
else q[i].k-=t,q2[++t2]=q[i];
}
}
for(int i=ql;i<=qr;i++) {
if(q[i].fl!=1) {
if(q[i].y<=mid) {
add(q[i].x,q[i].fl?1:-1);
}
}
}
int k=ql;
for(int i=1;i<=t1;i++,k++) q[k]=q1[i];
for(int i=1;i<=t2;i++,k++) q[k]=q2[i];
work(ql,ql+t1-1,L,mid),work(ql+t1,qr,mid+1,R);
}
int main() {
scanf("%d%d",&n,&m);
for(int i=1;i<=n;i++) {
q[i].x=i;
scanf("%d",&q[i].y);
a[i]=q[i].y;
}
cnt=n; int Q=0;
for(int i=1;i<=m;i++) {
scanf("%s",s+1);
if(s[1]=='Q') {
q[++cnt].fl=1;
scanf("%d%d%d",&q[cnt].x,&q[cnt].y,&q[cnt].k);
q[cnt].id=++Q;
} else {
int x,y; scanf("%d%d",&x,&y);
q[++cnt].fl=2,q[cnt].x=x,q[cnt].y=a[x];
q[++cnt].fl=0,q[cnt].x=x,q[cnt].y=y;
a[x]=y;
}
}
work(1,cnt,0,1e9);
for(int i=1;i<=Q;i++) {
printf("%d\n",ans[i]);
}
return 0;
}
例题1
整体二分后区间修改,区间查询,有线段树
WA了一次:读入是没开longlong
#include<bits/stdc++.h>
#define ll long long
using namespace std;
const int N=5e4+5,M=4e5+5;
int n,m,ans[N];
ll c[M],t[M];
struct A{int fl,l,r,id; ll k; }q[N],q1[N],q2[N];
inline void down(int p,int l,int r,int mid) {
if(t[p]) {
t[p<<1]+=t[p],c[p<<1]+=(ll)t[p]*(mid-l+1);
t[p<<1|1]+=t[p],c[p<<1|1]+=(ll)t[p]*(r-mid);
t[p]=0;
}
}
inline void up(int p) {
c[p]=c[p<<1]+c[p<<1|1];
}
void add(int p,int l,int r,int x,int y,int k) {
if(l==x&&r==y) {
c[p]+=k*(r-l+1),t[p]+=k; return;
}
int mid=l+r>>1;
down(p,l,r,mid);
if(y<=mid) add(p<<1,l,mid,x,y,k);
else if(x>mid) add(p<<1|1,mid+1,r,x,y,k);
else {
add(p<<1,l,mid,x,mid,k);
add(p<<1|1,mid+1,r,mid+1,y,k);
}
up(p);
}
ll ask(int p,int l,int r,int x,int y) {
if(l==x&&r==y) return c[p];
int mid=l+r>>1;
down(p,l,r,mid);
if(y<=mid) return ask(p<<1,l,mid,x,y);
if(x>mid) return ask(p<<1|1,mid+1,r,x,y);
return ask(p<<1,l,mid,x,mid)+ask(p<<1|1,mid+1,r,mid+1,y);
}
void work(int ql,int qr,int L,int R) {
if(ql>qr) return;
if(L==R) {
for(int i=ql;i<=qr;i++) {
if(q[i].fl==2) ans[q[i].id]=L;
}
return;
}
int mid=L+R>>1,t1=0,t2=0;
for(int i=ql;i<=qr;i++) {
if(q[i].fl==1) {
if(q[i].k>mid) {
add(1,1,n,q[i].l,q[i].r,1);
q2[++t2]=q[i];
} else q1[++t1]=q[i];
} else {
ll t=ask(1,1,n,q[i].l,q[i].r);
if(t>=q[i].k) q2[++t2]=q[i];
else q[i].k-=t,q1[++t1]=q[i];
}
}
for(int i=ql;i<=qr;i++) {
if(q[i].fl==1) {
if(q[i].k>mid) {
add(1,1,n,q[i].l,q[i].r,-1);
}
}
}
int k=ql;
for(int i=1;i<=t1;i++,k++) q[k]=q1[i];
for(int i=1;i<=t2;i++,k++) q[k]=q2[i];
work(ql,ql+t1-1,L,mid),work(ql+t1,qr,mid+1,R);
}
int main() {
scanf("%d%d",&n,&m); int Q=0;
for(int i=1;i<=m;i++) {
scanf("%d%d%d%lld",&q[i].fl,&q[i].l,&q[i].r,&q[i].k);
if(q[i].fl==2) q[i].id=++Q;
}
work(1,m,-1e9,1e9);
for(int i=1;i<=Q;i++) {
printf("%d\n",ans[i]);
}
return 0;
}
例题3
Luogu P3527 [POI2011]MET-Meteors
显然是否收集完具有单调性,所以考虑整体二分
由于每个操作最多做\(lg\)次,所以直接暴力枚举某个时间前的空间站的情况即可
#include<bits/stdc++.h>
#define ll long long
const int INF=1e9;
using namespace std;
const int N=3e5+5,M=1e6+5;
int n,m,ans[N],b[N];
ll c[M];
vector<int>V[N];
struct A{int fl,l,r,id; ll k; }q[M],q1[M],q2[M];
inline void add(int x,int k) {
for(;x<=m;x+=x&-x) c[x]+=k;
}
inline ll ask(int x) {
ll ret=0;
for(;x;x-=x&-x) ret+=c[x];
return min(ret,(ll)INF);
}
void work(int ql,int qr,int L,int R) {
if(ql>qr) return;
if(L==R) {
for(int i=ql;i<=qr;i++) {
if(q[i].fl) {
ans[q[i].id]=L;
}
}
return;
}
int mid=L+R>>1,t1=0,t2=0;
for(int i=ql;i<=qr;i++) {
if(!q[i].fl) {
if(q[i].id<=mid) {
add(q[i].l,q[i].k);
add(q[i].r+1,-q[i].k);
q1[++t1]=q[i];
} else q2[++t2]=q[i];
} else {
ll t=0;
for(int v:V[q[i].l]) {
t+=ask(v);
}
if(t>=q[i].k) q1[++t1]=q[i];
else q[i].k-=t,q2[++t2]=q[i];
}
}
for(int i=ql;i<=qr;i++) {
if(!q[i].fl&&q[i].id<=mid) {
add(q[i].l,-q[i].k);
add(q[i].r+1,q[i].k);
}
}
int k=ql;
for(int i=1;i<=t1;i++,k++) q[k]=q1[i];
for(int i=1;i<=t2;i++,k++) q[k]=q2[i];
work(ql,ql+t1-1,L,mid),work(ql+t1,qr,mid+1,R);
}
int main() {
scanf("%d%d",&n,&m); int Q=0,cnt=0;
for(int i=1;i<=m;i++) {
int t; scanf("%d",&t);
V[t].push_back(i);
}
for(int i=1;i<=n;i++) scanf("%d",&b[i]);
scanf("%d",&Q);
for(int i=1;i<=Q;i++) {
int l,r,k; scanf("%d%d%d",&l,&r,&k);
if(l<=r) {
q[++cnt].l=l,q[cnt].r=r,q[cnt].k=k,q[cnt].id=i;
} else {
q[++cnt].l=l,q[cnt].r=m,q[cnt].k=k,q[cnt].id=i;
q[++cnt].l=1,q[cnt].r=r,q[cnt].k=k,q[cnt].id=i;
}
}
q[++cnt].l=1,q[cnt].r=m,q[cnt].k=INF,q[cnt].id=Q+1;
for(int i=1;i<=n;i++) {
q[++cnt].fl=1,q[cnt].l=i,q[cnt].k=b[i],q[cnt].id=i;
}
work(1,cnt,1,Q+1);
for(int i=1;i<=n;i++) {
if(ans[i]!=Q+1) {
printf("%d\n",ans[i]);
} else puts("NIE");
}
return 0;
}
例题4
可以根据dfn判断子路径(分类讨论LCA)
然后变成区间覆盖,单点查询
考虑第K大,所以整体二分,变成平面修改,单点查询
由于保证了询问在修改前,所以可以离线采用扫描线+树状数组,可以事先按一维排序(一维相同,询问在修改后),在进行整体二分,时间复杂度$O(nlg^2n)
WA了一次:没考虑清lca重合的情况(红色表示选择范围,绿色表示链)
RE了一次:数组开小(LCA的)
#include<bits/stdc++.h>
#define ll long long
using namespace std;
const int N=4e4+5,M=2.4e5+5;
int n,m,q,id[N],sz[N],ans[N],c[N],b[M],fid[N];
struct A{int id,l,r,k,fh; }a[M],q1[M],q2[M];
vector<int>V[N];
namespace LCA {
int f[N][16],lg[N],d[N],sgn;
void dfs(int fa,int u) {
id[u]=++sgn,sz[u]=1,d[u]=!fa?0:d[fa]+1;
f[u][0]=fa;
for(int i=1;i<=lg[d[u]];i++) {
f[u][i]=f[f[u][i-1]][i-1];
}
for(int v:V[u]) {
if(v!=fa) {
dfs(u,v);
sz[u]+=sz[v];
}
}
}
inline int son(int u,int v) {
for(int i=lg[d[v]-d[u]];i>=0;i--) {
if(d[f[v][i]]>d[u]) v=f[v][i];
}
return v;
}
inline int lca(int u,int v) {
if(d[u]>d[v]) swap(u,v);
while(d[u]<d[v]) v=f[v][lg[d[v]-d[u]]];
if(u==v) return u;
for(int i=lg[d[u]];i>=0;i--) {
if(f[u][i]!=f[v][i]) {
u=f[u][i],v=f[v][i];
}
}
return f[u][0];
}
void init() {
lg[0]=-1;
for(int i=1;i<=n;i++) lg[i]=lg[i>>1]+1;
dfs(0,1);
}
}
inline void add(int x,int k) {
for(;x<=n;x+=x&-x) c[x]+=k;
}
inline int ask(int x) {
int ret=0;
for(;x;x-=x&-x) ret+=c[x];
return ret;
}
void work(int ql,int qr,int L,int R) {
if(ql>qr) return;
if(L==R) {
for(int i=ql;i<=qr;i++) {
if(!a[i].fh) {
ans[a[i].l]=L;
}
}
return;
}
int mid=L+R>>1,t1=0,t2=0;
for(int i=ql;i<=qr;i++) {
if(a[i].fh) {
if(a[i].k<=mid) {
q1[++t1]=a[i];
add(a[i].l,a[i].fh),add(a[i].r+1,-a[i].fh);
} else q2[++t2]=a[i];
} else {
int t=ask(a[i].r);
if(a[i].k<=t) q1[++t1]=a[i];
else a[i].k-=t,q2[++t2]=a[i];
}
}
for(int i=1;i<=t1;i++) a[ql+i-1]=q1[i];
for(int i=1;i<=t2;i++) a[ql+t1+i-1]=q2[i];
work(ql,ql+t1-1,L,mid),work(ql+t1,qr,mid+1,R);
}
inline bool cmp(A i,A j) {
return i.id<j.id||i.id==j.id&&abs(i.fh)>abs(j.fh);
}
inline bool cmp2(int i,int j) {
return a[i].k<a[j].k;
}
inline int rd() {
int ret=0; char ch=getchar();
while(ch<'0'||ch>'9') ch=getchar();
for(;ch>='0'&&ch<='9';ch=getchar()) ret=(ret<<1)+(ret<<3)+ch-'0';
return ret;
}
int main() {
n=rd(),m=rd(),q=rd();
for(int i=2;i<=n;i++) {
int u=rd(),v=rd();
V[u].push_back(v),V[v].push_back(u);
}
LCA::init(); int cnt=0;
for(int i=1;i<=m;i++) {
int u=rd(),v=rd(),k=rd();
int l=LCA::lca(u,v);
if(id[u]>id[v]) swap(u,v);
if(u!=l)
a[++cnt]=(A){id[u],id[v],id[v]+sz[v]-1,k,1};
a[++cnt]=(A){id[u]+sz[u],id[v],id[v]+sz[v]-1,k,-1};
} else {
u=LCA::son(u,v);
a[++cnt]=(A){1,id[v],id[v]+sz[v]-1,k,1};
a[++cnt]=(A){id[u],id[v],id[v]+sz[v]-1,k,-1};
if(id[u]+sz[u]<=n) {
a[++cnt]=(A){id[v],id[u]+sz[u],n,k,1};
a[++cnt]=(A){id[v]+sz[v],id[u]+sz[u],n,k,-1};
}
}
}
for(int i=1;i<=cnt;i++) b[i]=i;
sort(b+1,b+cnt+1,cmp2);
int num=0; fid[num]=a[b[1]].k,a[b[1]].k=num;
for(int i=2;i<=cnt;i++) {
if(a[b[i]].k!=fid[a[b[i-1]].k]) num++,fid[num]=a[b[i]].k;
a[b[i]].k=num;
}
for(int i=1;i<=q;i++) {
int u=rd(),v=rd(),k=rd();
if(id[u]>id[v]) swap(u,v);
a[++cnt]=(A){id[u],i,id[v],k,0};
}
sort(a+1,a+cnt+1,cmp);
work(1,cnt,0,num);
for(int i=1;i<=q;i++) {
printf("%d\n",fid[ans[i]]);
}
return 0;
}