[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} \times 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} \times B \times B)=O(nB)\)

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

根据基本不等式,\(B\)取\(\sqrt{m \log m}\)时最优.总时间复杂\(O(n\sqrt{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\n",ans);
}

[BZOJ 3509] [CodeChef] COUNTARI (FFT+分块)的更多相关文章

  1. bzoj 3509: [CodeChef] COUNTARI] [分块 生成函数]

    3509: [CodeChef] COUNTARI 题意:统计满足\(i<j<k, 2*a[j] = a[i] + a[k]\)的个数 \(2*a[j]\)不太好处理,暴力fft不如直接暴 ...

  2. BZOJ 3509: [CodeChef] COUNTARI

    3509: [CodeChef] COUNTARI Time Limit: 40 Sec  Memory Limit: 128 MBSubmit: 883  Solved: 250[Submit][S ...

  3. BZOJ 3509 [CodeChef] COUNTARI ——分块 FFT

    分块大法好. 块内暴力,块外FFT. 弃疗了,抄SX队长$silvernebula$的代码 #include <map> #include <cmath> #include & ...

  4. BZOJ3509 [CodeChef] COUNTARI 【分块 + fft】

    题目链接 BZOJ3509 题解 化一下式子,就是 \[2A[j] = A[i] + A[k]\] 所以我们对一个位置两边的数构成的生成函数相乘即可 但是由于这样做是\(O(n^2logn)\)的,我 ...

  5. CodeChef - COUNTARI FTT+分块

    Arithmetic Progressions Given N integers A1, A2, …. AN, Dexter wants to know how many ways he can ch ...

  6. BZOJ3509: [CodeChef] COUNTARI

    3509: [CodeChef] COUNTARI Time Limit: 40 Sec  Memory Limit: 128 MBSubmit: 339  Solved: 85[Submit][St ...

  7. bzoj 3513: [MUTC2013]idiots FFT

    bzoj 3513: [MUTC2013]idiots FFT 链接 bzoj 思路 参考了学姐TRTTG的题解 统计合法方案,最后除以总方案. 合法方案要不好统计,统计不合法方案. \(a+b< ...

  8. BZOJ 3343: 教主的魔法(分块+二分查找)

    BZOJ 3343: 教主的魔法(分块+二分查找) 3343: 教主的魔法 Time Limit: 10 Sec  Memory Limit: 256 MBSubmit: 1172  Solved:  ...

  9. CodeChef COUNTARI Arithmetic Progressions(分块 + FFT)

    题目 Source http://vjudge.net/problem/142058 Description Given N integers A1, A2, …. AN, Dexter wants ...

随机推荐

  1. c# 操作mysql数据库的时候会出现 插入中文汉字变成问号?

    场景: 在mysql ce里面执行时没有问题的. c#操作会出现问号. 原因是:  链接字符串的时候 要设置Charset=utf8; 不然就会按默认的服务器设置编码,通常会出问题. 检查: 1.创建 ...

  2. jmeter--单个接口通,自动化不通时

    单个接口通,自动化不通时,对比两者请求 post 请求的格式,内容编码

  3. Linux培训教程 linux中nl命令使用介绍

    nl命令在linux系统中用来计算文件中行号.nl 可以将输出的文件内容自动的加上行号!其默认的结果与 cat -n 有点不太一样, nl 可以将行号做比较多的显示设计,包括位数与是否自动补齐 0 等 ...

  4. 由于数据库 'XXX' 离线,无法打开该数据库。

    大家使用sql server 进行还原的时候可能会遇到: system.Data.SqlClient.SqlError:因为数据库正在使用,所以无法获得对数据库的独占访问权 这个错误 下面是有一个解决 ...

  5. 51Nod 1433 0和5 (数论 && 被9整除数的特点)

    题意 : 小K手中有n(1~1000)张牌, 每张牌上有一个一位数的数, 这个字数不是0就是5.小K从这些牌在抽出任意张(不能抽0张), 排成一行这样就组成了一个数.使得这个数尽可能大, 而且可以被9 ...

  6. 一个强大的json解析工具类

    该工具类利用递归原理,能够将任意结构的json字符串进行解析.当然,如果需要解析为对应的实体对象时,就不能用了 package com.wot.cloudsensing.carrotfarm.util ...

  7. ubuntu下安装apatch

    在Ubuntu上安装Apache,有两种方式:1 使用开发包的打包服务,例如使用apt-get命令:2 从源码构建Apache.本文章将详细描述这两种不同的安装方式. 方法一:使用开发包的打包服务—— ...

  8. tcpdump指定IP和端口抓包

    如下指定抓www.baidu.com 并且80端口的包 保存到test.cap 可以在Windows下面用wireshark打开 tcpdump 'port 80 and host www.baidu ...

  9. webpack安装大于4.x版本(没有配置webpack.config.js)

    webpack安装大于4.x版本(没有配置webpack.config.js) webpack 输出参数-o 高版本  如果安装的webpack版本大于4+,还需要安装webpack-cli.在没有配 ...

  10. webpack前置知识1(模块化开发)

    webpack前置知识1(模块化开发) 新建 模板 小书匠  在开始对模块化开发进行讲解之前,我们需要有这么一个认识,即 在没有过多第三方干扰时,成本低收益高的事物更容易获得推广和信赖. 模块化开发就 ...