zoukankan      html  css  js  c++  java
  • [BZOJ 3509] [CodeChef] COUNTARI (FFT+分块)

    [BZOJ 3509] [CodeChef] COUNTARI (FFT+分块)

    题面

    给出一个长度为n的数组,问有多少三元组((i,j,k))满足(i<j<k,a_j-a_i=a_k-a_j)

    (n leq 10^5, a_i leq 30000)

    分析

    (m=max(a_i))

    先做一点变形(a_i+a_k=2a_j).那么枚举j,把j左边的和右边的所有数字找出来,找出有哪些数字对的和为(2a_j)

    维护当前块左边每个数的出现次数cntl,,和右边块的出现次数cntr。

    那么数字和为(2a_j)的方案数就是(sum_{i=0}^{2a_j} cntl(i)cntr(2a_j-i))

    这是一个卷积的形式,可以FFT.令:

    $A(x)=sum_{i=0}^{m} cntl(i) x^i $

    $B(x)=sum_{i=0}^{m} cntr(i) x^i $

    (A*B)的第(2a_j)项的系数就是答案.但是做n次FFT的复杂度是(O(n^2 log n)),显然是跑不过去的.

    我们要平衡一下复杂度,容易想到分块。设块大小为T

    i,j,k均不在同一个块的情况

    我们枚举j在第几个块,从左到右扫描第2~n-1个块,每块把左边和右边FFT卷积。枚举块内的每个数x,和为(2x)的方案数就是卷积结果里第(2x)项的系数.

    时间复杂度(O(frac{n}{B} imes m log m))

    i,j,k至少有2个在同一个块的情况

    直接暴力从小到大在块内枚举(k,j(j<k)).然后讨论j在k前还是在k后。同样维护2个数组cntl表示当前块前面的数的出现次数,cntr表示k后面的数的个数。

    如果i在j前,那么答案就是(cntl[a_j*2-a_k])

    如果i在k后,那么答案就是(cntr[a_k*2-a_j])

    注意到cntl不包含当前块,而cntr包含当前块,这样(i,j,k)在同一个块的情况只会被算1次

    时间复杂度(O(frac{n}{B} imes B imes B)=O(nB))

    总时间复杂度(O(nB+frac{n}{B}mlog m))

    根据基本不等式,(B)(sqrt{m log m})时最优.总时间复杂(O(nsqrt{m log m}))

    代码

    #include<iostream>
    #include<cstdio>
    #include<cstring>
    #include<cmath>
    #define maxn 200000
    using namespace std;
    typedef long double db;
    typedef long long ll;
    const db pi=acos(-1.0);
    struct com{
        double real;
        double imag;
        com(){
    
        } 
        com(double _real,double _imag){
            real=_real;
            imag=_imag;
        }
        com(double x){
            real=x;
            imag=0;
        }
        void operator = (const com x){
            this->real=x.real;
            this->imag=x.imag;
        }
        void operator = (const double x){
            this->real=x;
            this->imag=0;
        }
        friend com operator + (com p,com q){
            return com(p.real+q.real,p.imag+q.imag);
        }
        friend com operator + (com p,double q){
            return com(p.real+q,p.imag);
        }
        void operator += (com q){
            *this=*this+q;
        }
        void operator += (double q){
            *this=*this+q;
        }
        friend com operator - (com p,com q){
            return com(p.real-q.real,p.imag-q.imag);
        }
        friend com operator - (com p,double q){
            return com(p.real-q,p.imag);
        }
        void operator -= (com q){
            *this=*this-q;
        }
        void operator -= (double q){
            *this=*this-q;
        }
        friend com operator * (com p,com q){
            return com(p.real*q.real-p.imag*q.imag,p.real*q.imag+p.imag*q.real);
        }
        friend com operator * (com p,double q){
            return com(p.real*q,p.imag*q);
        } 
        void operator *= (com q){
            *this=(*this)*q;
        }
        void operator *= (double q){
            *this=(*this)*q;
        }
        friend com operator / (com p,double q){
            return com(p.real/q,p.imag/q);
        } 
        void operator /= (double q){
            *this=(*this)/q;
        } 
        void print(){
            printf("%lf + %lf i ",real,imag);
        }
    };
    void fft(com *x,int n,int type){
    	static int rev[maxn+5];
    	int dn=1,k=0;
    	while(dn<n){
    		dn*=2;
    		k++;
    	}
    	for(int i=0;i<n;i++) rev[i]=(rev[i>>1]>>1)|((i&1)<<(k-1));
    	for(int i=0;i<n;i++) if(i<rev[i]) swap(x[i],x[rev[i]]);
    	for(int len=1;len<n;len*=2){
    		int sz=len*2;
    		com wn1=com(cos(2*pi/sz),sin(2*pi/sz)*type);
    		for(int l=0;l<n;l+=sz){
    			int r=l+len-1;
    			com wnk=1;
    			for(int i=l;i<=r;i++){
    				com tmp=x[i+len];
    				x[i+len]=x[i]-wnk*tmp;
    				x[i]=x[i]+wnk*tmp;
    				wnk*=wn1;
    			}
    		}
    	}
    	if(type==-1) for(int i=0;i<n;i++) x[i]/=n;
    }
    void mul(com *a,com *b,com *ans,int n){
    	fft(a,n,1);
    	if(a!=b) fft(b,n,1);
    	for(int i=0;i<n;i++) ans[i]=a[i]*b[i];
    	fft(ans,n,-1);
    }
    
    int n;
    int bsz,bcnt;
    int a[maxn+5]; 
    int bel[maxn+5];
    int lb[maxn+5],rb[maxn+5];//预处理每个数的左右边界,卡常 
    void ini_block(){
    	bsz=sqrt(n*log(n)/log(2));
    	bcnt=1;
    	for(int i=1;i<=n;i++){
    		bel[i]=bcnt;
    		if(i%bsz==0) bcnt++;
    	}
    	for(int i=1;i<=bcnt;i++){
    		lb[i]=bsz*(i-1)+1;
    		rb[i]=min(i*bsz,n); 
    	}
    } 
    
    int cntl[maxn+5],cntr[maxn+5];
    com fl[maxn+5],fr[maxn+5],res[maxn+5];
    int main(){
    	scanf("%d",&n);
    	ini_block();
    	int maxv=0;
    	for(int i=1;i<=n;i++){
    		scanf("%d",&a[i]);
    		maxv=max(maxv,a[i]);
    	} 
    	//i,j,k有>=2个在同一块内,直接暴力O(n/B*B^2)=O(Bn)
    	ll ans=0;
    	for(int i=n;i>=1;i--) cntr[a[i]]++;
    	for(int id=1;id<=bcnt;id++){
    		//cntl记录的是当前块前面的数的出现情况
    		//cntr记录的是i后面的数的出现情况
    		//注意i,j,k均在一个块内的情况只能被算1次  
    		for(int k=lb[id];k<=rb[id];k++){
    			cntr[a[k]]--;
    			for(int j=lb[id];j<k;j++){
    				int ai=a[j]*2-a[k];
    				if(ai>=0&&ai<=maxn) ans+=cntl[ai]; 
    				ai=a[k]*2-a[j];
    				if(ai>=0&&ai<=maxn) ans+=cntr[ai];//这样只有这个时候会算到i,j,k在同一个块里的情况 
    			}
    			
    		}
    		for(int j=lb[id];j<=rb[id];j++) cntl[a[j]]++;
    	} 
    	memset(cntl,0,sizeof(cntl));
    	memset(cntr,0,sizeof(cntr));
    	for(int i=n;i>=lb[2];i--) cntr[a[i]]++;
    	for(int i=1;i<=rb[1];i++) cntl[a[i]]++; 
    	for(int id=2;id<bcnt;id++){
    		for(int j=lb[id];j<=rb[id];j++) cntr[a[j]]--;
    		for(int j=0;j<=maxv;j++){
    			fl[j]=cntl[j];
    			fr[j]=cntr[j];
    		}
    		int dn=1;
    		while(dn<=maxv*2) dn*=2;
    		mul(fl,fr,res,dn);
    		for(int j=lb[id];j<=rb[id];j++){
    			ans+=(ll)(res[a[j]*2].real+0.5);
    		}
    		for(int j=0;j<=dn;j++){
    			fl[j]=0;
    			fr[j]=0;
    			res[j]=0;
    		}
    		for(int j=lb[id];j<=rb[id];j++) cntl[a[j]]++;
    	} 
    	printf("%lld
    ",ans);
    }
    
  • 相关阅读:
    搭建非域AlwaysOn win2016+SQL2016
    从0开始搭建SQL Server AlwaysOn 第四篇(配置异地机房节点)
    从0开始搭建SQL Server AlwaysOn 第二篇(配置故障转移集群)
    从0开始搭建SQL Server AlwaysOn 第三篇(配置AlwaysOn)
    从0开始搭建SQL Server AlwaysOn 第一篇(配置域控)
    四、基于Windows 2012配置SQL Server 2014 AlwaysOn
    三、安装SQLserver 2014(For AlwaysOn)
    二、 Windows 2012配置故障转移(For SQLServer 2014 AlwaysOn)
    Mybatis-SQL语句构建器类及日志
    Mybatis-JavaAPI
  • 原文地址:https://www.cnblogs.com/birchtree/p/11715737.html
Copyright © 2011-2022 走看看