zoukankan      html  css  js  c++  java
  • 二次离线莫队学习笔记

    做了两天二离,还是lxl那个Ynoi毒瘤,卡常卡了一天啊

    理论部分

    其实也没啥好说的,我对二离的理解就是加速莫队端点的移动。

    但是目前看起来,二离的局限性在于维护的信息必须可减。

    举个例子,比如我们现在需要移动端点 ([l,r] o [l,r']) ,那么正常莫队的写法是每移动一次就修改一次贡献。

    当移动端点不是 (O(1)) 的时候,由于总的移动路径长是 (O(msqrt n)) 的,复杂度会再乘上移动复杂度,不那么优秀。

    二离就是来优化这个过程的。

    我们发现我们只需要预处理出端点移动时产生的贡献就可以 (O(1)) 移动端点,然后端点移动时产生的贡献就成功被平衡掉了。

    接下来简略讲讲怎么平衡。

    一般都是拆前缀和的形式。比如 ([l,r] o [l,r+1]) 的时候,我们统计 (r+1)([1,l-1]) 的贡献以及 ([1,r]) 的贡献,减一下就好了。 (r+1)([1,r]) 的贡献很特殊,可以预处理。这样我们只需算出 ([1,l-1])(r) 的贡献即可,所以要支持 (O(sqrt n)) 修改一个集合,完成 (O(1)) 查询一个端点对集合的答案即可做到 (O(nsqrt n)) 了(因为总移动路径长是 (O(nsqrt n)) 的)。

    当然复杂度再高就会导致二离成为瓶颈而非莫队,这时候要小心。不过到现在为止做到的题都是刚好平衡成莫队复杂度。

    大概口胡的部分就这么点,剩下的还是要看题目,很多东西要在实践中发现。

    P5047 [Ynoi2019模拟赛]Yuno loves sqrt technology II

    区间逆序对,要求空间 (O(n))

    (O(nsqrt{n}log n)) 应该谁都会做,而且谁都知道被卡了。

    先把所有询问按照莫队的方法排序。

    考虑按照上面的说法拆询问。

    首先预处理 ([1,x])(x+1) 的逆序对数,这个树状数组跑一趟就好了。

    记上面那个东西的前缀和为 (pre_x)

    (f([l,r],x)) 表示区间 ([l,r])(x) 的贡献。

    ([l,r] o [l,r+1]) : 每一个询问拆成 (f([1,r],r)-f([1,l-1],r))

    那么 ([l,r] o[l,r'](r'>r)) 拆成 (pre_{r'}-pre_{r}) 减去 ([1,l-1])([r+1,r']) 间每一个点的贡献,询问挂到 (l-1) 上。

    注意这里的 (r) 并不用减一,因为贡献是从 (r+1) 开始算的。

    ([l,r] o [l-1,r]) :发现前缀好像不够,于是我又搞了个后缀 (suf) 表示 (x)([x+1,n]) 的贡献 (事实上是我zz了,后面会讲怎么不用后缀,但是现在这样好理解)。那么询问拆成 (f([l,n],l-1)-f([r+1,n],l-1))

    那么 ([l,r] o[l',r](l'<l)) 拆成 (suf_{l'}-suf_{l}) 减去 ([r+1,n])([l',l-1]) 间每一个点的贡献,询问挂到 (r+1) 上。

    这里我把扩展区间写到了前面,因为先删除区间可能出现 (l>r) 的情况无意义,我也不知道会出啥事,可能也能AC吧,反正能避掉这种无意义情况就避掉了。

    剩下就很类似了:

    ([l,r] o[l,r'](r>r')) 拆成 (pre_{r}-pre_{r'}) 减去 ([1,l-1])([r'+1,r]) 的贡献。注意这个对于答案的系数是 (-1) ,而 (r') 的贡献不应该被减去。

    ([l,r] o[l',r](l'<l)) 拆成 (suf_{l}-suf_{l'}) 减去 ([l',r])([l,l'-1]) 的贡献,对答案的贡献还是 (-1)

    于是我们有了 (2m) 个二次离线的询问,每一个都是 ([1,x])([l,r]) 贡献的形式(后缀同理)

    这时候我们不能带 (log) 。有个非常强大的东西:分块。这东西博大精深。lxl最清楚。

    我们对于值域分块(值域太大就离散化,反正具体大小不影响逆序对,相对大小才重要)

    现在我们面对的是单点修改前缀和,单点查值的问题,要求查询 (O(1))

    (x) 属于块 (bel_x) ,对于整块打标记,每一个块内再记前缀和。

    对于小于 (bel_x) 的块直接打标记,(O(dfrac{n}{S})) 完成,设这一块的标记为 (tag_x)

    对于 (x) 所在块的左端点到 (x) ,再修改块内前缀和 (sum_x)

    那么查询的时候 (sum_x+tag_x) 就是答案了,(O(1)) 了。

    我们从 (1 o i) 遍历所有离线下来的询问,先修改这一位,然后处理所有挂在 (i) 上的询问。

    综上,我们会先离线出 (2m) 个询问,然后利用莫队性质遍历 (nsqrt n) 级别的长度 (O(1)) 回答每一个询问,然后再跑一次莫队,然而指针的移动已经 (O(1)) 了,只需要 (O(m))

    当然这个也可以改成前缀和,只不过在离线的时候要多记一个系数。其实这个东西本质是处理相邻两个询问的差。

    #include<bits/stdc++.h>
    using namespace std;
    #define fi first
    #define se second
    #define mkp(x,y) make_pair(x,y)
    #define pb(x) push_back(x)
    #define sz(v) (int)v.size()
    typedef long long LL;
    typedef double db;
    template<class T>bool ckmax(T&x,T y){return x<y?x=y,1:0;}
    template<class T>bool ckmin(T&x,T y){return x>y?x=y,1:0;}
    #define rep(i,x,y) for(int i=x,i##end=y;i<=i##end;++i)
    #define per(i,x,y) for(int i=x,i##end=y;i>=i##end;--i)
    inline int read(){
        int x=0,f=1;char ch=getchar();
        while(!isdigit(ch)){if(ch=='-')f=0;ch=getchar();}
        while(isdigit(ch))x=x*10+ch-'0',ch=getchar();
        return f?x:-x;
    }
    const int N=100005;
    const int S=320;
    const int B=N/S+5;
    int n,m,num,a[N],pre[N],suf[N],L[B],R[B],bel[N];
    LL sp[N],ss[N],res[N<<1];
    LL ans[N];
    int lsh[N],len;
    int tr[N];
    int tol,tor;
    struct QUE{
    	int l,r,id;
    	inline bool operator< (const QUE&t)const{return bel[l]!=bel[t.l]?l<t.l:bel[l]&1?r<t.r:r>t.r;}
    }q[N];
    struct ASK{
    	int pos,l,r,id;
    	inline bool operator < (const ASK&t)const{return pos<t.pos;}
    }ql[N],qr[N];
    namespace BLOCK1{
    int sum[N],tag[B];
    void update(int x,int d){
    	for(int i=1;i<bel[x];++i)tag[i]+=d;
    	for(int i=L[bel[x]];i<=x;++i)sum[i]+=d;
    }
    int query(int x){
    	return sum[x]+tag[bel[x]];
    }
    }
    namespace BLOCK2{
    int sum[N],tag[B];
    void update(int x,int d){
    	for(int i=num;i>bel[x];--i)tag[i]+=d;
    	for(int i=x;i<=R[bel[x]];++i)sum[i]+=d;
    }
    int query(int x){
    	return sum[x]+tag[bel[x]];
    }
    }
    
    signed main(){
    	n=read(),m=read(),num=(n-1)/S+1;
    	rep(i,1,num)L[i]=R[i-1]+1,R[i]=i*S;R[num]=n;
    	rep(i,1,num)rep(j,L[i],R[i])bel[j]=i;
    	rep(i,1,n)a[i]=lsh[i]=read();
    	sort(lsh+1,lsh+n+1),len=unique(lsh+1,lsh+n+1)-lsh-1;
    	rep(i,1,n)a[i]=lower_bound(lsh+1,lsh+len+1,a[i])-lsh;
    	rep(i,1,n){
    		int res=0;
    		for(int j=a[i]+1;j<=n;j+=j&-j)res+=tr[j];
    		pre[i]=res;
    		for(int j=a[i];j>0;j-=j&-j)++tr[j];
    	}
    	fill(tr,tr+n+1,0);
    	per(i,n,1){
    		int res=0;
    		for(int j=a[i]-1;j>0;j-=j&-j)res+=tr[j];
    		suf[i]=res;
    		for(int j=a[i];j<=n;j+=j&-j)++tr[j];
    	}
    	rep(i,1,m)q[i].id=i,q[i].l=read(),q[i].r=read();
    	sort(q+1,q+m+1);
    	for(int i=1,l=1,r=0;i<=m;++i){
    		if(l>q[i].l)ql[++tol].pos=r+1,ql[tol].l=q[i].l,ql[tol].r=l-1,ql[tol].id=q[i].id<<1,l=q[i].l;
    		if(r<q[i].r)qr[++tor].pos=l-1,qr[tor].l=r+1,qr[tor].r=q[i].r,qr[tor].id=q[i].id<<1|1,r=q[i].r;
    		if(l<q[i].l)ql[++tol].pos=r+1,ql[tol].l=l,ql[tol].r=q[i].l-1,ql[tol].id=q[i].id<<1,l=q[i].l;
    		if(r>q[i].r)qr[++tor].pos=l-1,qr[tor].l=q[i].r+1,qr[tor].r=r,qr[tor].id=q[i].id<<1|1,r=q[i].r;
    	}
    	sort(ql+1,ql+tol+1),sort(qr+1,qr+tor+1);
    	for(int i=1,j=1;i<=n;++i){
    		while(j<=tor&&qr[j].pos<i)++j;
    		BLOCK1::update(a[i],1),sp[i]=sp[i-1]+pre[i];
    		for(;j<=tor&&qr[j].pos==i;++j){
    			for(int k=qr[j].l;k<=qr[j].r;++k)
    				res[qr[j].id]+=BLOCK1::query(a[k]+1);
    		}
    	}
    	for(int i=n,j=tol;i>=1;--i){
    		while(j>=1&&ql[j].pos>i)--j;
    		BLOCK2::update(a[i],1),ss[i]=ss[i+1]+suf[i];
    		for(;j>=1&&ql[j].pos==i;--j){
    			for(int k=ql[j].l;k<=ql[j].r;++k)
    				res[ql[j].id]+=BLOCK2::query(a[k]-1);
    		}
    	}
    	LL now=0;
    	for(int i=1,l=1,r=0;i<=m;++i){
    		if(l>q[i].l)now+=ss[q[i].l]-ss[l]-res[q[i].id<<1],l=q[i].l;
    		if(r<q[i].r)now+=sp[q[i].r]-sp[r]-res[q[i].id<<1|1],r=q[i].r;
    		if(l<q[i].l)now-=ss[l]-ss[q[i].l]-res[q[i].id<<1],l=q[i].l;
    		if(r>q[i].r)now-=sp[r]-sp[q[i].r]-res[q[i].id<<1|1],r=q[i].r;
    		ans[q[i].id]=now;
    	}
    	rep(i,1,m)printf("%lld
    ",ans[i]);
    	return 0;
    }
    

    (O(nsqrt n))(2) 倍常数还能 150ms 跑完挺令人吃惊的。

    P5501 [LnOI2019]来者不拒,去者不追

    有了上一道题,这题应该很板子了吧!万事开头难,理解了二离这种题应该随便切了。

    移动端点的贡献显然是:一个区间内小于 (a_x) 的数的个数 乘 (a_x) ,加上大于 (a_x) 的数的和。

    这个东西直接套路值域分块即可,没啥好讲的。

    #include<bits/stdc++.h>
    using namespace std;
    #define fi first
    #define se second
    #define mkp(x,y) make_pair(x,y)
    #define pb(x) push_back(x)
    #define sz(v) (int)v.size()
    typedef long long LL;
    typedef double db;
    template<class T>bool ckmax(T&x,T y){return x<y?x=y,1:0;}
    template<class T>bool ckmin(T&x,T y){return x>y?x=y,1:0;}
    #define rep(i,x,y) for(int i=x,i##end=y;i<=i##end;++i)
    #define per(i,x,y) for(int i=x,i##end=y;i>=i##end;--i)
    #define getchar() (p1==p2&&(p2=(p1=buf)+fread(buf,1,1<<21,stdin),p1==p2)?EOF:*p1++)
    char buf[1<<21],*p1=buf,*p2=buf;
    inline int read(){
        int x=0,f=1;char ch=getchar();
        while(!isdigit(ch)){if(ch=='-')f=0;ch=getchar();}
        while(isdigit(ch))x=x*10+ch-'0',ch=getchar();
        return f?x:-x;
    }
    const int N=500005;
    const int M=100005;
    int n,m,a[N],S,V,num;
    int bel[N],L[N],R[N];
    int pr1[N],sf1[N],t1[M];
    LL  pr2[N],sf2[N],t2[M];
    LL sp1[N],ss1[N],sp2[N],ss2[N];
    LL res1[N<<1],res2[N<<1],ans[N];
    int tl,tr;
    
    struct QUE{
    	int id,l,r;
    	QUE(){l=r=id=0;}
    	inline bool operator < (const QUE&t)const{return bel[l]!=bel[t.l]?l<t.l:bel[l]&1?r<t.r:r>t.r;}
    }q[N];
    struct ASK{
    	int pos,id,l,r;
    	ASK(){pos=id=l=r=0;}
    	inline bool operator < (const ASK&t)const{return pos<t.pos;}
    }ql[N],qr[N];
    
    namespace BLO{
    
    int num,S,L[N],R[N],bel[N];
    int sum1[N],tag1[N];
    LL sum2[N],tag2[N];
    void init(){
    	S=sqrt(V-1)+1,num=(V-1)/S+1;
    	rep(i,1,num)L[i]=R[i-1]+1,R[i]=i*S;R[num]=V;
    	rep(i,1,num)rep(j,L[i],R[i])bel[j]=i;
    }
    void clear(){
    	memset(sum1,0,sizeof(sum1)),memset(tag1,0,sizeof(tag1));
    	memset(sum2,0,sizeof(sum2)),memset(tag2,0,sizeof(tag2));	
    }
    void update1(int x,int d){
    	for(int i=num;i>bel[x];--i)tag1[i]+=d;
    	for(int i=R[bel[x]];i>=x;--i)sum1[i]+=d;
    }
    int query1(int x){return sum1[x]+tag1[bel[x]];}
    void update2(int x,int d){
    	for(int i=1;i<bel[x];++i)tag2[i]+=d;
    	for(int i=L[bel[x]];i<=x;++i)sum2[i]+=d;
    }
    LL query2(int x){return sum2[x]+tag2[bel[x]];}
    }
    
    signed main(){
    	n=read(),m=read(),
    	S=sqrt(n-1)+1,num=(n-1)/S+1;
    	rep(i,1,num)L[i]=R[i-1]+1,R[i]=i*S;R[num]=n;
    	rep(i,1,num)rep(j,L[i],R[i])bel[j]=i;
    	rep(i,1,n)ckmax(V,a[i]=read());
    	rep(i,1,m)q[i].l=read(),q[i].r=read(),q[i].id=i;
    	sort(q+1,q+m+1);
    	
    	for(int i=1;i<=n;++i){
    		int r1=0;LL r2=0;
    		for(int j=a[i]-1;j>0;j-=j&-j)r1+=t1[j];
    		for(int j=a[i]+1;j<=V;j+=j&-j)r2+=t2[j];
    		pr1[i]=r1,pr2[i]=r2,sp1[i]=sp1[i-1]+r1,sp2[i]=sp2[i-1]+r2;
    		for(int j=a[i];j<=V;j+=j&-j)++t1[j];
    		for(int j=a[i];j>0;j-=j&-j)t2[j]+=a[i];
    	}
    	memset(t1,0,sizeof(t1)),memset(t2,0,sizeof(t2));
    	for(int i=n;i>=1;--i){
    		int r1=0;LL r2=0;
    		for(int j=a[i]-1;j>0;j-=j&-j)r1+=t1[j];
    		for(int j=a[i]+1;j<=V;j+=j&-j)r2+=t2[j];
    		sf1[i]=r1,sf2[i]=r2,ss1[i]=ss1[i+1]+r1,ss2[i]=ss2[i+1]+r2;
    		for(int j=a[i];j<=V;j+=j&-j)++t1[j];
    		for(int j=a[i];j>0;j-=j&-j)t2[j]+=a[i];
    	}
    
    	rep(i,1,n)sp1[i]=sp1[i-1]+1ll*a[i]*(pr1[i]+1);
    	rep(i,1,n)sp2[i]=sp2[i-1]+pr2[i];
    	per(i,n,1)ss1[i]=ss1[i+1]+1ll*a[i]*(sf1[i]+1);
    	per(i,n,1)ss2[i]=ss2[i+1]+sf2[i];
    
    	for(int i=1,l=1,r=0;i<=m;++i){
    		// cerr<<q[i].id<<' '<<q[i].l<<' '<<q[i].r<<' '<<l<<' '<<r<<'
    ';
    		if(l>q[i].l)ql[++tl].pos=r+1,ql[tl].l=q[i].l,ql[tl].r=l-1,ql[tl].id=q[i].id<<1,l=q[i].l;
    		if(r<q[i].r)qr[++tr].pos=l-1,qr[tr].l=r+1,qr[tr].r=q[i].r,qr[tr].id=q[i].id<<1|1,r=q[i].r;
    		if(l<q[i].l)ql[++tl].pos=r+1,ql[tl].l=l,ql[tl].r=q[i].l-1,ql[tl].id=q[i].id<<1,l=q[i].l;
    		if(r>q[i].r)qr[++tr].pos=l-1,qr[tr].l=q[i].r+1,qr[tr].r=r,qr[tr].id=q[i].id<<1|1,r=q[i].r;
    	}
    	sort(ql+1,ql+tl+1),sort(qr+1,qr+tr+1);
    
    	BLO::init();
    	for(int i=1,j=1;i<=n;++i){
    		while(j<=tr&&qr[j].pos<i)++j;
    		BLO::update1(a[i],1),BLO::update2(a[i],a[i]);
    		for(;j<=tr&&qr[j].pos==i;++j){
    			int st=qr[j].l,ed=qr[j].r,id=qr[j].id;
    			for(int k=st;k<=ed;++k)
    				res1[id]+=1ll*BLO::query1(a[k]-1)*a[k],res2[id]+=BLO::query2(a[k]+1);
    			// cerr<<"qr:"<<i<<' '<<qr[j].l<<' '<<qr[j].r<<' '<<res1[id]<<' '<<res2[id]<<'
    ';
    		}
    	}
    	BLO::clear();
    	for(int i=n,j=tl;i>=1;--i){
    		while(j>=1&&ql[j].pos>i)--j;
    		BLO::update1(a[i],1),BLO::update2(a[i],a[i]);
    		for(;j>=1&&ql[j].pos==i;--j){
    			int st=ql[j].l,ed=ql[j].r,id=ql[j].id;
    			for(int k=st;k<=ed;++k)
    				res1[id]+=1ll*BLO::query1(a[k]-1)*a[k],res2[id]+=BLO::query2(a[k]+1);
    			// cerr<<"ql:"<<i<<' '<<ql[j].l<<' '<<ql[j].r<<' '<<res1[id]<<' '<<res2[id]<<'
    ';
    		}
    	}
    
    	LL now=0;
    	for(int i=1,l=1,r=0;i<=m;++i){
    		int id=q[i].id;
    		if(l>q[i].l)now+=ss1[q[i].l]-ss1[l]-res1[id<<1]+ss2[q[i].l]-ss2[l]-res2[id<<1],l=q[i].l;
    		if(r<q[i].r)now+=sp1[q[i].r]-sp1[r]-res1[id<<1|1]+sp2[q[i].r]-sp2[r]-res2[id<<1|1],r=q[i].r;
    		if(l<q[i].l)now-=ss1[l]-ss1[q[i].l]-res1[id<<1]+ss2[l]-ss2[q[i].l]-res2[id<<1],l=q[i].l;
    		if(r>q[i].r)now-=sp1[r]-sp1[q[i].r]-res1[id<<1|1]+sp2[r]-sp2[q[i].r]-res2[id<<1|1],r=q[i].r;
    		ans[id]=now;
    	}
    	rep(i,1,m)printf("%lld
    ",ans[i]);
    	return 0;
    }
    

    P4887 【模板】莫队二次离线(第十四分块(前体))

    讲一个重要的优化:两次变一次。即只用处理前缀,不用处理后缀,这样常数会小一些。为下一道终极Boss铺垫。

    我们使用后缀的原因就是因为左端点移动。但是后缀和也可以用前缀和算啊。于是:

    左端点左移:([l,r] o[l-1,r]) ,拆成 (f([l,r],l-1)=f([1,r],l-1)-f([1,l-1],l-1))

    注意后面那个东西与我们之前统计的 (f([1,x-1],x)) 是有差别的,要重新处理一遍。(就一个修改前统计,一个修改完统计)

    再来看看这题具体怎么搞。

    (cnt[a_iigoplus a_j]=k)(cnt) 表示这个数二进制下有几个 (1)) 等价于 (cnt[a_iigoplus x]=a_j,cnt[x]=k)

    每次加入一个数就暴力枚举所有 (cnt[x]=k) 的数,加到桶里面就好了。

    你可能好奇我的码风为啥忽然变了,因为我忽然回来搞二离纯粹是因为觉得过个板子啥用都没有,于是重新搞7个月之前学的算法。其实这才是我最先做的二离,这代码是7个月前的。。。

    注意,对于挂询问我们 不应该vector ,常数太大了,还不如排序,反正才 (O(m)) 个。远古代码懒得改了。

    #include<bits/stdc++.h>
    using namespace std;
    typedef long long LL;
    #define rint register int
    #define getchar() (p1==p2&&(p2=(p1=buf)+fread(buf,1,1<<21,stdin),p1==p2)?EOF:*p1++)
    char buf[1<<21],*p1=buf,*p2=buf;
    inline int rd() {
    	int x=0,f=1;char ch=getchar();
    	while(!isdigit(ch)) {if(ch=='-')f=-1;ch=getchar();}
    	while(isdigit(ch))x=x*10+(ch^48),ch=getchar();
    	return x*f;
    }
    const int N=100010;
    int n,m,k,a[N];
    int k_2[4000],cnt;
    int size,bel[N];
    int bin[N];
    LL sum1[N],sum2[N];
    LL res[N<<1];
    LL ans[N],cur;
    struct QUE {
    	int l,r,id;
    	QUE(){}
    	QUE(int _l,int _r,int _i):l(_l),r(_r),id(_i){}
    }q[N];
    vector<QUE>qn[N];
    bool cmp(const QUE &a,const QUE &b) {
    	return bel[a.l]!=bel[b.l]?a.l<b.l:bel[a.l]&1?a.r<b.r:a.r>b.r;
    }
    #define pb push_back
    #define ITv vector<QUE>::iterator
    signed main() {
    	n=rd(),m=rd(),k=rd(),size=sqrt(n);
    	for(rint i=0;i<16384;++i) {
    		int x=i,t=0;
    		while(x)x^=x&-x,++t;
    		if(t==k)k_2[++cnt]=i;
    	}
    	for(rint i=1;i<=n;++i)a[i]=rd(),bel[i]=(i-1)/size+1;
    	for(rint i=1;i<=m;++i)q[i].id=i,q[i].l=rd(),q[i].r=rd();
    	sort(q+1,q+m+1,cmp);
    	for(rint i=1,l=q[1].r+1,r=q[1].r;i<=m;++i) {
    		if(l<q[i].l)qn[r].pb(QUE(l,q[i].l-1,q[i].id<<1));
    		else if(l>q[i].l)qn[r].pb(QUE(q[i].l,l-1,q[i].id<<1));
    		l=q[i].l;
    		if(r<q[i].r)qn[l-1].pb(QUE(r+1,q[i].r,q[i].id<<1|1));
    		else if(r>q[i].r)qn[l-1].pb(QUE(q[i].r+1,r,q[i].id<<1|1));
    		r=q[i].r;
    	}
    	for(rint i=1;i<=n;++i) {
    		sum1[i]=sum1[i-1]+bin[a[i]];
    		for(rint j=1;j<=cnt;++j)++bin[a[i]^k_2[j]];
    		sum2[i]=sum2[i-1]+bin[a[i]];
    		for(ITv j=qn[i].begin();j!=qn[i].end();++j)
    			for(rint k=j->l;k<=j->r;++k)
    				res[j->id]+=bin[a[k]];
    	}
    	for(rint i=1,l=q[1].r+1,r=q[1].r;i<=m;++i) {
    		if(l<q[i].l)cur+=sum2[q[i].l-1]-sum2[l-1]-res[q[i].id<<1];
    		else if(l>q[i].l)cur+=sum2[q[i].l-1]-sum2[l-1]+res[q[i].id<<1];
    		l=q[i].l;
    		if(r<q[i].r)cur+=sum1[q[i].r]-sum1[r]-res[q[i].id<<1|1];
    		else if(r>q[i].r)cur+=sum1[q[i].r]-sum1[r]+res[q[i].id<<1|1];
    		r=q[i].r,ans[q[i].id]=cur;
    	}
    	for(rint i=1;i<=m;++i)printf("%lld
    ",ans[i]);
    	return 0;
    }
    

    P5398 [Ynoi2018]GOSICK

    hoho!卡常卡了一天。话说 (color{black}{ exttt{z}}color{red}{ exttt{houkangyang}}) 好像把lxl的标算卡T了,所以,虽然你们现在看到的是 3s,说不定过不了多久就4s了,就不用卡常了!

    这题真正的难点并非二离,而是如何处理离线下来的询问。准确来说是卡常

    首先显然的一点,一个数加入的贡献为:区间中,它作为倍数的次数 加上 它作为因数的次数。

    它作为因数很好处理,加入每一个数的时候开值域上的桶,加入它的所有因数即可,这东西严格小于 (sqrt n) ,而且预处理之后跑不满。

    它作为倍数有些困难。很容易想到阈值分治。设阈值 (S)

    大于 (S) 的数加入的时候暴力枚举倍数加到上面的桶里就是 (O(dfrac{n}{S})) 的了。

    小于 (S) 的就很鬼畜了。我一开始让想不出来,问同学,让同学支持的操作是:往集合里加一个数。查询某个数作为倍数的出现次数,询问有 (nsqrt n) 个。于是被认为不可做题。

    其实二离要灵活用,我们要注意询问是被离线成区间的形式的,而且区间只有 (O(m)) 个。

    而一个数作为倍数我们可以换种方法,统计某一个数作为因数的出现次数。

    一个数作为 (le S) 的数的倍数的贡献,转化成 (le S) 的数作为因数的贡献,这个直接枚举每一个小于 (S) 的数就好了,统计一个前缀和就可以 (O(1)) 回答一个区间的答案,复杂度是 (O(nS))

    于是我们阈值分治之后复杂度上限是 (O(nsqrt n+dfrac{n^2}{S}+nS))

    看起来 (S=sqrt n) 就做完了。对,这是看起来。

    写完后发现这个东西跑的贼慢,大概是大量cache miss造成的,而且本身 (5e5sqrt{5e5}), 就很吃力,后面虽然是 (nS) ,但是 (S) 开到200就几乎吃不消了。

    这时候前面 (dfrac{n^2}{S}) 的复杂度可以直接被卡飞。

    于是点开了题解。

    艹,动态寻找最优阈值真nb。

    具体来说,我们发现一个阈值的贡献是 (nS+sum [a_i>S]dfrac{V}{a_i})(V) 是值域)

    而且我们还可以对于 (nS) 部分带一个常数来平衡cache miss造成的常数增大。

    于是这东西有效减小了常数,然后还是被卡常了。

    对了,如果你TLE0了,不应该认定是你写挂了,因为正确复杂度很有可能就是0分。我一开始还是前后扫两遍的,就TLE0了。

    说起来我怎么AC的题也真离谱,86分的代码不断交,差不多把一天中各个时段都试了一遍就AC了。没想到还是高峰期AC的大草。原因是洛谷忽然不明原因开始随机RE,而且评测机忽然变快了。后来随机RE刚结束赶紧交了一发,果然变快了,然后最大点2.98sAC。。。

    给一个疑似卡lxl标算的提交链接。link

    第一个点是随机的,第二个点是非常强劲的hack数据,第三个点是针对DPair卡的。

    大概只有srz能过了,因为他不是动态块长的,而且最大点2.5s不到/fad。

    upd:srz也跑了3s+。目前没有代码能跑进3s

    貌似GOSICK现在的数据阈值直接开70就能过/fad

    const int N=500005;
    int n,m,a[N];
    int V,S,bin[N],bel[N];
    LL pre[N],ans[N];
    int tot;
    int cnt[N],tim[N];
    LL suf[N];
    vector<int>d[N];
    struct QUE{
    	int id,l,r;
    	QUE(){id=l=r=0;}
    	inline bool operator < (const QUE&t)const{return bel[l]!=bel[t.l]?l<t.l:bel[l]&1?r<t.r:r>t.r;}
    }q[N];
    struct ASK{
    	int pos,id,op,l,r;
    	ASK(){pos=l=r=op=id=0;}
    	inline bool operator < (const ASK&t)const{return pos<t.pos;}
    }qn[N<<1];
    void init(){
    	for(int i=1;i<=n;++i)suf[a[i]]+=V/a[i];
    	for(int i=V;i>=1;--i)suf[i]+=suf[i+1];
    	LL mx=1e15;
    	for(int i=1;i*i<=V;++i)if(ckmin(mx,5ll*i*n+suf[i+1]))S=i;
    	for(int i=1;i<=V;++i)for(int j=1;i*j<=V;++j)d[i*j].pb(i);
    }
    signed main(){
    	clock_t ST=clock(),ED;
    	n=read(),m=read();
    	int blocksize=sqrt(n-1)+1;
    	rep(i,1,n)ckmax(V,a[i]=read()),bel[i]=(i-1)/blocksize+1;
    	init();
    	cerr<<S<<'
    ';
    	for(int i=1;i<=n;++i){
    		int x=a[i],res=bin[x];
    		for(int j=0,up=sz(d[x]);j<up;++j){
    			if(d[x][j]>S)break;
    			res+=tim[d[x][j]];
    		}
    		pre[i]=pre[i-1]+res;
    		for(int j=0,up=sz(d[x]);j<up;++j)++bin[d[x][j]];
    		if(x>S)for(int j=x;j<=V;j+=x)++bin[j];
    		else ++tim[x];
    	}
    	rep(i,1,m)q[i].l=read(),q[i].r=read(),q[i].id=i;
    	sort(q+1,q+m+1);
    	for(int i=1,l=1,r=0;i<=m;++i){
    		if(l>q[i].l){
    			if(r<=n)qn[++tot].pos=r,qn[tot].id=q[i].id,qn[tot].l=q[i].l,qn[tot].r=l-1,qn[tot].op=1;
    			ans[q[i].id]-=pre[l-1]-pre[q[i].l-1]+(l-q[i].l)*2,l=q[i].l;
    		}
    		if(r<q[i].r){
    			if(l>1)qn[++tot].pos=l-1,qn[tot].id=q[i].id,qn[tot].l=r+1,qn[tot].r=q[i].r,qn[tot].op=-1;
    			ans[q[i].id]+=pre[q[i].r]-pre[r],r=q[i].r;
    		}
    		if(l<q[i].l){
    			if(r<=n)qn[++tot].pos=r,qn[tot].id=q[i].id,qn[tot].l=l,qn[tot].r=q[i].l-1,qn[tot].op=-1;
    			ans[q[i].id]+=pre[q[i].l-1]-pre[l-1]+(q[i].l-l)*2,l=q[i].l;
    		}
    		if(r>q[i].r){
    			if(l>1)qn[++tot].pos=l-1,qn[tot].id=q[i].id,qn[tot].l=q[i].r+1,qn[tot].r=r,qn[tot].op=1;
    			ans[q[i].id]-=pre[r]-pre[q[i].r],r=q[i].r;
    		}
    	}
    	sort(qn+1,qn+tot+1);
    	memset(bin,0,sizeof(bin));
    	for(int i=1,j=1;i<=n;++i){
    		int x=a[i];
    		for(int k=0,up=sz(d[x]);k<up;++k)++bin[d[x][k]];
    		if(x>S)for(int k=x;k<=V;k+=x)++bin[k];
    		for(;j<=tot&&qn[j].pos==i;++j){
    			int st=qn[j].l,ed=qn[j].r; LL sum=0;
    			for(int k=st;k<=ed;++k)sum+=bin[a[k]];
    			ans[qn[j].id]+=1ll*sum*qn[j].op;
    		}
    	}
    	ED=clock(),cerr<<1.*(ED-ST)/CLOCKS_PER_SEC<<'
    ';
    	for(int v=1;v<=S;++v){
    		for(int i=1;i<=n;++i)cnt[i]=cnt[i-1]+(a[i]%v==0),tim[i]=tim[i-1]+(a[i]==v);
    		for(int i=1;i<=tot;++i)ans[qn[i].id]+=1ll*(cnt[qn[i].r]-cnt[qn[i].l-1])*tim[qn[i].pos]*qn[i].op;
    	}
    	rep(i,1,m)ans[q[i].id]+=ans[q[i-1].id];
    	rep(i,1,m)ans[q[i].id]+=q[i].r-q[i].l+1;
    	rep(i,1,m)write(ans[i],'
    ');
    	// rep(i,1,m)printf("%lld
    ",ans[i]);
    	fwrite(cltout,1,oh-cltout,stdout),oh=cltout;
    	ED=clock(),cerr<<1.*(ED-ST)/CLOCKS_PER_SEC<<'
    ';
    	return 0;
    }
    

    代码直接给吧,直接交保证你AC不掉。

    upd:又交了一发AC了大草,于是只保留主要部分/yun

  • 相关阅读:
    jdk环境变量及1.6官方下载地址
    linux创建用户和用户组
    java左移右移运算符
    浏览器是如何存储密码的
    【转】 ip段/数字,如192.168.0.1/24是什么意思?
    动态代理实现AOP【转】
    java自定义注解注解方法、类、属性等等【转】
    oracle插入主键数据、sequence和触发器
    ThreadLocal意为变量副本
    【转】java内部类的作用
  • 原文地址:https://www.cnblogs.com/zzctommy/p/14161535.html
Copyright © 2011-2022 走看看