P4477 [BJWC2018]基础匹配算法练习题
题面传送门
题解区居然没有一个写理论复杂度最优的算法的。那我就来讲一下。
首先显然有一个结论:对于一个\(b_i\),和小于\(z-b_i\)且最大的\(a_i\)连线时最优的。因为这样可以为大于当前\(b_i\)的提供更优条件。
同时没有修改就可以考虑离线算法,比如莫队。
莫队的增加时就是把当前\(b_i\)按照上面的方法在平衡树中找到答案删除。同时维护答案。
但是减却不是很好搞。就可以考虑回滚莫队。
时间复杂度大概是\(O(m\sqrt mlogm)\)
但是fhq-treap有一个特殊性质,就是可以\(O(m)\)建树,所以可以把\(logm\)扔进去变成\(O(m\sqrt{nlogn})\)
代码实现:
#include<bits/stdc++.h>
#define l(x) f[x].l
#define r(x) f[x].r
#define CI const int &
#define max(a,b) ((a)>(b)?(a):(b))
#define min(a,b) ((a)<(b)?(a):(b))
using namespace std;
int n,m,k,ks,q,x,y,z,a[160039],b[160039],head,ans[160039],tot,now,st[160039],sh,lasttop,l;
struct yyy{int x,y,id;}s[160039];
struct ques{int l,r,key,sum;};
struct fhq{
int cnt,root,root1,root2,root3;ques f[160039];
int a[160039];
inline int newnode(int x){f[++cnt]=(ques){0,0,rand(),x};return cnt;}
inline void split(int now,int x,int &a,int &b){
if(!now){a=b=0;return;}
if(f[now].sum<=x) a=now,split(r(now),x,r(now),b);
else b=now,split(l(now),x,a,l(now));
}
inline int merge(int x,int y){
if(!x||!y) return x|y;
if(f[x].key<f[y].key) return r(x)=merge(r(x),y),x;
else return l(y)=merge(x,l(y)),y;
}
inline void clear(){cnt=0;}
inline int build(int last,CI l=1,CI r=n){
if(l>r)return 0;
int m=l+r>>1,now=newnode(a[m]);f[now].key=f[last].key+rand();
l(now)=build(now,l,m-1);r(now)=build(now,m+1,r);return now;
}
inline int last(int x){
split(root,x,root1,root2);
int now=root1;if(!root1) return 0;
while(r(now))now=r(now);now=f[now].sum;
st[++sh]=now;split(root1,now-1,root1,root3);root3=merge(l(root3),r(root3));root=merge(root1,merge(root3,root2));return 1;
}
inline void get(int x){split(root,x,root1,root2);root=merge(root1,merge(newnode(x),root2));}
}f;
inline bool cmp(yyy x,yyy y){return x.x/ks==y.x/ks?x.y<y.y:x.x<y.x;}
inline void swap(int &x,int &y){x^=y^=x^=y;}
int main(){
// freopen("1.in","r",stdin);
register int i,j,h;
scanf("%d%d%d",&n,&m,&k);ks=max(sqrt(m/log2(m)),1);
for(i=1;i<=n;i++) scanf("%d",&f.a[i]);
sort(f.a+1,f.a+n+1);
for(i=1;i<=m;i++) scanf("%d",&b[i]);
scanf("%d",&q);f.root=f.build(0);
for(i=1;i<=q;i++){
scanf("%d%d",&x,&y);
if(x/ks==y/ks){
tot=0;
for(j=x;j<=y;j++) tot+=f.last(k-b[j]);
ans[i]=tot;
while(sh) f.get(st[sh--]);
}
else s[++head]=(yyy){x,y,i};
}
sort(s+1,s+head+1,cmp);
for(i=1;i<=head;i++){
f.clear();f.root=f.build(0);now=i;while(now<head&&s[now+1].x/ks==s[i].x/ks) now++;l=s[i].x/ks*ks+ks;tot=0;
for(j=i;j<=now;j++){
while(l<=s[j].y)tot+=f.last(k-b[l++]);
lasttop=sh;ans[s[j].id]=tot;
for(h=s[i].x/ks*ks+ks-1;h>=s[j].x;h--) tot+=f.last(k-b[h]);
swap(ans[s[j].id],tot);while(sh!=lasttop) f.get(st[sh--]);
sh=0;
}
i=now;
}
for(i=1;i<=q;i++) printf("%d\n",ans[i]);
}
但是这个常数很大很大,所以只能退而求其次变成小常数权值线段树,时间复杂度\(O(m\sqrt mlogm)\)
注意撤销时不要全部撤销,撤销一部分即可,常数会小很多。
代码实现:
#include<bits/stdc++.h>
#define l(x) x<<1
#define r(x) x<<1|1
#define CI const int &
#define R register
#define max(a,b) ((a)>(b)?(a):(b))
#define min(a,b) ((a)<(b)?(a):(b))
using namespace std;
int n,m,k,ks,q,x,y,z,b[160039],head,ans[160039],st[160039],lasttop,a[160039],sh;
int nows[300039],tots[300039],l,r,mid,f[900039];
struct yyy{int x,y,id;}s[160039];
struct ques{int l,r,key,sum;};
inline void ls(int &x){for(l=0,r=n+m;l+1<r;)mid=l+r>>1,(x>nows[mid])?(l=mid):(r=mid);x=tots[r];}
inline bool cmp(yyy x,yyy y){return x.x/ks==y.x/ks?x.y<y.y:x.x<y.x;}
inline void swap(int &x,int &y){x^=y^=x^=y;}
inline void get(R int x,R int y,R int l=1,R int r=n+m,R int now=1){
R int mid;
while(l!=r)mid=l+r>>1,f[now]+=y,(x<=mid)?(r=mid,now=l(now)):(l=mid+1,now=r(now));
f[now]+=y;
}
inline int find(R int x,R int l=1,R int r=n+m,R int now=1){
R int mid,ans=0;
while(l!=r) mid=l+r>>1,(x<=mid)?(r=mid,now=l(now)):(l=mid+1,ans+=f[l(now)],now=r(now));
return ans+f[now];
}
inline int query(R int x,R int l=1,R int r=n+m,R int now=1){
R int mid;
while(l!=r)mid=l+r>>1,(x<=f[l(now)])?(r=mid,now=l(now)):(x-=f[l(now)],l=mid+1,now=r(now));
return l;
}
inline int last(R int x){
R int ans=find(x);if(!ans) return 0;
ans=query(ans);st[++sh]=ans;get(ans,-1);return 1;
}
signed main(){
// freopen("1.in","r",stdin);
// freopen("1.out","w",stdout);
R int i,j,h,tot=0,now=0,lasttop=0;
scanf("%d%d%d",&n,&m,&k);ks=min(sqrt(m),m);
for(i=1;i<=n;i++) scanf("%d",&a[i]),nows[i]=a[i];
sort(a+1,a+n+1);
for(i=1;i<=m;i++) scanf("%d",&b[i]),nows[i+n]=k-b[i];
sort(nows+1,nows+n+m+1);
for(i=1;i<=n+m;i++)tots[i]=(i^1)?(tots[i-1]+(nows[i]!=nows[i-1])):1;
for(i=1;i<=n;i++) ls(a[i]);
for(i=1;i<=m;i++) b[i]=k-b[i],ls(b[i]);
scanf("%d",&q);
for(i=1;i<=n;i++) get(a[i],1);
for(i=1;i<=q;i++){
scanf("%d%d",&x,&y);
if(x/ks==y/ks){
tot=0;for(j=x;j<=y;j++) tot+=last(b[j]);ans[i]=tot;
while(sh) get(st[sh--],1);
}
else s[++head]=(yyy){x,y,i};
}
sort(s+1,s+head+1,cmp);
for(i=1;i<=head;i++){
now=i;while(now<head&&s[now+1].x/ks==s[i].x/ks) now++;l=s[i].x/ks*ks+ks;tot=0;
for(j=i;j<=now;j++){
while(l<=s[j].y)
tot+=last(b[l++]);
lasttop=sh;ans[s[j].id]=tot;
for(h=s[i].x/ks*ks+ks-1;h>=s[j].x;h--) tot+=last(b[h]);
swap(ans[s[j].id],tot);while(sh!=lasttop) get(st[sh--],1);
}//printf("%d\n",i);
while(sh) get(st[sh--],1);
i=now;
}
for(i=1;i<=q;i++) printf("%d\n",ans[i]);
}