整体二分基础
还是放模板题的一篇博客。话说今天和分治挺有缘,上午cdq,下午整体分治。
整体分治为何物?一种特殊的分治,和cdq一样是在朴素分治上加了外挂,只不过cdq是参照的归并,整体是参照的二分查找。
众所周知二分查找可是个好东西,但问题在于它需要一定的预处理,比如让序列有序。这就让它在询问次数很多时显得有点力不从心,但没关系,我们有整体二分。
整体二分的要求和cdq一样,需要允许离线的题目,但它支持修改。而且能用整体二分写的题目一般都能用树套树来解决,但它常数小一点(3到7倍的样子),写起来也要简单一点。
整体二分的思路就是对于值域进行二分,每次得出来的mid不只是针对某一个询问,而是针对所有包含了这个点的询问,这样就充分利用了我们的算力,这不挺好?
有两个模板题。第一个是主席树的模板题。题意是多次询问区间第k大,当然可以用主席树来做,但既然博客题目是整体二分,那就不写主席树吧。做法上就是考虑递归处理,假如当前处理的值域是l和r,它们产生了一个mid,那么我们就可以把所有处在[l,mid]之中的数的位置标记为1,然后对于每个询问进行查询即可,这类题一般用的树状数组维护。边界就是左右指针重合,这说明这一系列的询问答案都会是当前的值。
#include<cstdio>
#include<algorithm>
//#define zczc
using namespace std;
const int N=200010;
const int maxn=1e9;
inline void read(int &wh){
wh=0;int f=1;char w=getchar();
while(w<'0'||w>'9'){if(w=='-')f=-1;w=getchar();}
while(w<='9'&&w>='0'){wh=wh*10+w-'0';w=getchar();}
wh*=f;return;
}
int m,n,cnt;
#define lowbit (wh&-wh)
int t[N];
inline void change(int wh,int val){
for(;wh<=m;wh+=lowbit)t[wh]+=val;
}
inline int work(int wh){
int an=0;
for(;wh;wh-=lowbit)an+=t[wh];
return an;
}
#undef lowbit
int ans[N];
struct node{
int op,id,l,r,k;
}q[N<<1],s1[N<<1],s2[N<<1];
void solve(int wl,int wr,int l,int r){
if(wl>wr)return;
if(l==r){
for(int i=wl;i<=wr;i++)ans[q[i].id]=l;
return;
}
int mid=l+r>>1,t1=0,t2=0;
for(int i=wl;i<=wr;i++){
if(q[i].op==1)continue;
if(q[i].l<=mid){
change(q[i].r,1);
s1[++t1]=q[i];
}
else s2[++t2]=q[i];
}
for(int i=wl;i<=wr;i++){
if(q[i].op==0)continue;
int now=work(q[i].r)-work(q[i].l-1);
if(now>=q[i].k)s1[++t1]=q[i];
else q[i].k-=now,s2[++t2]=q[i];
}
for(int i=wl;i<=wr;i++){
if(q[i].op==1)continue;
if(q[i].l<=mid)change(q[i].r,-1);
}
for(int i=1;i<=t1;i++)q[wl+i-1]=s1[i];
for(int i=1;i<=t2;i++)q[wl+t1+i-1]=s2[i];
solve(wl,wl+t1-1,l,mid);
solve(wl+t1,wr,mid+1,r);
return;
}
signed main(){
#ifdef zczc
freopen("in.txt","r",stdin);
#endif
read(m);read(n);
for(int i=1;i<=m;i++){
cnt++;read(q[i].l);
q[cnt].op=0;q[cnt].r=i;
}
for(int i=1;i<=n;i++){
cnt++;
read(q[cnt].l);read(q[cnt].r);read(q[cnt].k);
q[cnt].id=i,q[cnt].op=1;
}
solve(1,cnt,-maxn,maxn);
for(int i=1;i<=n;i++)printf("%d\n",ans[i]);
return 0;
}
另一个是Dynamic Rankings,题目一样,只是要求修改。修改其实都还好,由于读入的时候默认是按时间来的,处理的时候来就按顺序可以了。
#include<cstdio>
#include<algorithm>
//#define zczc
using namespace std;
const int N=100010;
const int maxn=1e9;
inline void read(int &wh){
wh=0;int f=1;char w=getchar();
while(w<'0'||w>'9'){if(w=='-')f=-1;w=getchar();}
while(w<='9'&&w>='0'){wh=wh*10+w-'0';w=getchar();}
wh*=f;return;
}
inline bool get(){
char w=getchar();
while(w!='Q'&&w!='C')w=getchar();
return w=='Q';
}
int m,n,cnt,sr[N];
struct node{
int op,id,l,r,val;
}a[N*3],s1[N*3],s2[N*3];
#define lowbit (wh&-wh)
int t[N],nt[N],ti;
inline void change(int wh,int val,int wt){
for(;wh<=m;wh+=lowbit){
if(nt[wh]!=wt)nt[wh]=wt,t[wh]=0;
t[wh]+=val;
}
return;
}
inline int work(int wh,int wt){
int an=0;
for(;wh;wh-=lowbit){
if(nt[wh]==wt)an+=t[wh];
}
return an;
}
#undef lowbit
int ans[N];
void solve(int wl,int wr,int l,int r){
if(wl>wr)return;
if(l==r){
for(int i=wl;i<=wr;i++)ans[a[i].id]=l;
return;
}
ti++;int mid=l+r>>1,t1=0,t2=0;
for(int i=wl;i<=wr;i++){
if(a[i].op==0){
if(a[i].l<=mid){
change(a[i].r,a[i].val,ti);
s1[++t1]=a[i];
}
else s2[++t2]=a[i];
}
else{
int now=work(a[i].r,ti)-work(a[i].l-1,ti);
if(now>=a[i].val)s1[++t1]=a[i];
else{
a[i].val-=now;
s2[++t2]=a[i];
}
}
}
for(int i=1;i<=t1;i++)a[wl+i-1]=s1[i];
for(int i=1;i<=t2;i++)a[wl+t1+i-1]=s2[i];
solve(wl,wl+t1-1,l,mid);
solve(wl+t1,wr,mid+1,r);
return;
}
signed main(){
#ifdef zczc
freopen("in.txt","r",stdin);
#endif
read(m);read(n);
for(int i=1;i<=m;i++){
cnt++;read(sr[i]);
a[cnt].l=sr[i];a[i].r=i;
a[cnt].op=false;a[cnt].val=1;
}
int s1,s2,s3,nid=0;
for(int i=1;i<=n;i++){
if(get()){
read(s1);read(s2);read(s3);
cnt++;
a[cnt].op=true,a[cnt].id=++nid;
a[cnt].l=s1,a[cnt].r=s2,a[cnt].val=s3;
}
else{
read(s1);read(s2);
cnt++;
a[cnt].op=false;
a[cnt].l=sr[s1];a[cnt].r=s1;a[cnt].val=-1;
cnt++;
a[cnt].op=false;
a[cnt].l=s2;a[cnt].r=s1;a[cnt].val=1;
sr[s1]=s2;
}
}
solve(1,cnt,0,maxn);
for(int i=1;i<=nid;i++)printf("%d\n",ans[i]);
return 0;
}
最后说一句,负数域一定要用右移而不是除法!
一如既往,万事胜意