• 【BZOJ3509】[CodeChef] COUNTARI(分块好题)


    点此看题面

    大致题意: 给定一个序列,问有多少对(i,j,k)满足(i<j<k)(a_k-a_j=a_j-a_i)

    从暴力到分块

    看到(a_k-a_j=a_j-a_i),我们容易想到把它移项得到(a_i+a_k=2a_j)

    于是有个很暴力的想法:枚举(j),然后统计(fr_x,bk_x)分别表示(j)前后(a_i=x)(i)的个数,则每次对(fr)(bk)做个(FFT),其中第(2a_j)位的系数就是能与(j)匹配的(i,k)对数。

    这个做法的正确性应该是显然的,而且这个做法复杂度很大会T飞也是显然的。

    考虑能不能对这个算法进行优化,然后发现相邻若干次(FFT)其实(fr,bk)变化很小,有很多计算都是白白浪费的。

    因此,我们祭出分块大法,假设把原序列分成若干大小为(S)的块,然后分情况讨论。

    情况(1):三点分属于不同块中

    显然此时我们可以枚举(j)在哪一个块(假设为第(t)个块),然后(i,k)就分别只能在第(1sim t-1)个块和第(t+1sim n)个块中。

    于是我们再拿出前面的(fr_x,bk_x),分别表示第(1sim t-1)个块和第(t+1sim n)个块中(a_i=x)(i)的个数。

    按照暴力的想法我们对(fr_x,bk_x)做个(FFT),然后枚举块中的(j)计算答案。

    时间复杂度:(O(frac nS imes nlogn))

    情况(2):三点中存在属于同一块中的点

    假设枚举(p,q(p<q))两点属于同一块(另一点(w)可以不在同一块,也可以在同一块,但不能在(p,q)中间)。

    我们同样统计(fr_x,bk_x)(我想应该不用再重复一次它们的定义了。。。),于是又分三种情况:

    • (w)在之前的块:则(p)对应(j),给答案加上(fr_{2 imes a_p-a_q})
    • (w)在之后的块:则(q)对应(j),给答案加上(bk_{2 imes a_q-a_p})
    • (w)在同一块:显然我们只需考虑(w<p)的情况(若(w>q),则在枚举到(q,w)时会重复计算贡献),然后我们发现实际上只要把(fr_x)改为统计(1sim p-1)的所有数中(a_i=x)(i)的个数,就可以把这种情况和(w)在之前的块的情况一并计算了,而这个统计显然是可以在枚举(p)的过程中同时维护的。

    时间复杂度:(O(nS))

    关于块的大小(S)

    我们发现这道题中两种情况下复杂度是不等的。

    我们令(frac nS imes nlogn=nS),然后就得到(S=sqrt{nlogn}),实测了几种块的大小似乎这也的确是最优的。

    代码

    #pragma GCC optimize(2)
    #pragma GCC optimize("inline")
    #include<bits/stdc++.h>
    #define Tp template<typename Ty>
    #define Ts template<typename Ty,typename... Ar>
    #define Reg register
    #define RI Reg int
    #define Con const
    #define CI Con int&
    #define I inline
    #define W while
    #define N 100000
    #define V 30000
    #define DB double
    using namespace std;
    int n,sz,a[N+5],fr[2*V+5],bk[2*V+5];long long res[2*V+5];
    class Poly//FFT模板
    {
    	private:
    		int P,L,R[V<<2];DB Pi;
    		struct node
    		{
    			DB x,y;I node(Con DB& a=0,Con DB& b=0):x(a),y(b){}
    			I node operator + (Con node& o) Con {return node(x+o.x,y+o.y);}
    			I node operator - (Con node& o) Con {return node(x-o.x,y-o.y);}
    			I node operator * (Con node& o) Con {return node(x*o.x-y*o.y,x*o.y+y*o.x);}
    		}A[V<<2],B[V<<2];
    		I void T(node *s,CI op)
    		{
    			RI i,j,k;node U,S,x,y;for(i=0;i^P;++i) i<R[i]&&(x=s[i],s[i]=s[R[i]],s[R[i]]=x,0);
    			for(i=1;i^P;i<<=1) for(U=node(cos(Pi/i),op*sin(Pi/i)),j=0;j^P;j+=i<<1)
    				for(S=node(1,0),k=0;k^i;++k,S=S*U) s[j+k]=(x=s[j+k])+(y=S*s[i+j+k]),s[i+j+k]=x-y;
    		}
    	public:
    		I Poly()
    		{
    			Pi=acos(-1),P=1,L=0;W(P<=(V<<1)) P<<=1,++L;
    			for(RI i=0;i^P;++i) R[i]=(R[i>>1]>>1)|((i&1)<<L-1);
    		}
    		I void FFT(int *x,int *y,long long *res)
    		{
    			RI i;for(i=0;i^P;++i) A[i]=B[i]=node();
    			for(i=0;i<=V;++i) A[i]=x[i],B[i]=y[i];
    			for(T(A,1),T(B,1),i=0;i^P;++i) A[i]=A[i]*B[i];
    			for(T(A,-1),i=0;i<=(V<<1);++i) res[i]=A[i].x/P+0.5;
    		}
    }P;
    int main()
    {
    	RI i,j,k,lim;long long ans=0;scanf("%d",&n),sz=sqrt(n*(log(n)/log(2)));
    	for(i=1;i<=n;++i) scanf("%d",a+i);
    	for(i=1;i<=sz;++i) ++fr[a[i]];for(i=sz+1;i<=n;++i) ++bk[a[i]];//统计fr,bk(方便起见一开始把第2个块统计在bk)
    	for(i=2;i*sz<n;++i)//情况1(注意无需枚举首尾两个块)
    	{
    		for(j=(i-1)*sz+1;j<=i*sz;++j) --bk[a[j]];P.FFT(fr,bk,res);//删去当前块的贡献,然后FFT
    		for(j=(i-1)*sz+1;j<=i*sz;++j) ++fr[a[j]],ans+=res[2*a[j]];//加上当前块的贡献,同时统计答案
    	}
    	for(i=0;i<=V;++i) fr[i]=bk[i]=0;for(i=1;i<=n;++i) ++bk[a[i]];//清空,重新统计bk
    	for(i=1;i<=(n-1)/sz+1;++i)//情况2
    	{
    		for(lim=min(i*sz,n),j=(i-1)*sz+1;j<=lim;++j) --bk[a[j]];//删去当前块的贡献
    		for(j=(i-1)*sz+1;j<=lim;++j)//枚举一对点
    		{
    			for(k=j+1;k<=lim;++k) 2*a[j]>=a[k]&&
    				(ans+=fr[2*a[j]-a[k]]),2*a[k]>=a[j]&&(ans+=bk[2*a[k]-a[j]]);//统计答案
    			++fr[a[j]];//加上当前点贡献
    		}
    	}return printf("%lld",ans),0;
    }
    
  • 相关阅读:
    HSV 量化
    小议:部署SharePoint 2013时,无法连接SQL Server问题
    BZOJ 1492 货币兑换 cdq分治或平衡树维护凸包
    初识ASP.NET---一般处理程序
    Vue.js
    jQuery内部原理和实现方式浅析
    关于js中namespace命名空间模式
    MutationObserver 监听DOM树变化
    TCP/UDP常见端口参考
    运算符优先级
  • 原文地址:https://www.cnblogs.com/chenxiaoran666/p/BZOJ3509.html
Copyright © 2020-2023  润新知