BZOJ4836 [Lydsy1704月赛]二元运算 分治 多项式 FFT
原文链接http://www.cnblogs.com/zhouzhendong/p/8830036.html
题目传送门 - BZOJ4836
题意
定义二元运算$opt$满足
$$x\ opt\ y=\begin{cases}x+y & \text{$(x<y)$} \\ x-y & \text{$(x\geq y)$}\end{cases}$$
现在给定一个长为$n$的数列$a$和一个长为$m$的数列$b$,接下来有$q$次询问。每次询问给定一个数字$c$你需要求出有多少对$(i, j)$使得$a_i\ opt\ b_j=c$。
题解
这题看了标签差不多就是傻逼题QAQ。
标签:分治+FFT
好了题解就这么点QAQ。
居然写了半个小时还好1A不然完蛋。
$\vdots$
$\vdots$
$\vdots$
$\vdots$
$\vdots$
$\vdots$
$\vdots$
$\vdots$
$\vdots$
$\vdots$
$\vdots$
$\vdots$
$\vdots$
$\vdots$
$\vdots$
$\vdots$
$\vdots$
还是来正经点的吧。
首先不考虑括号里的条件,假如分别针对两种运算$x+y$和$x-y$来写,该怎么办?
$FFT$套路啊。
我们对于$a$数组搞$50001$个桶,其中下标为$i$的桶保存到就是$a_x=i$的$x$个数。
对于$b$也同理。
假装$a_i$表示原先的$a$数组中$i$的个数,$b_i$同理。
然后你要快速算$x+y$相同的,写下式子:
$$h_i=\sum_{j=0}^{i}a_jb_{i-j}$$
显然可以$FFT$优化。
然后你要算$x-y$相同的,你假装桶有负的下标,对于每一个$i$,使$b_{-i}=b_i$。
然后让$b$整体向下标的正方向移动$50000$个单位(对于有效的前$50001$个,这个操作的效果就是将原来的$50001$个桶翻转),就可以列出和原来的那个差不多的式子,同样也可以$FFT$。
但是原运算法则中有括号里面的条件啊。
我们要强力满足这个条件。
于是我们采用分治。
对数值进行分治。
我们分$3$类对最终答案进行贡献。
假装我们把区间分成了$[L,mid]$和$(mid,R]$。
$1.x=y$,只要再长度为$1$的区间内自己贡献一下就可以了。具体看代码。
$2.x<y$,那我们只要让$a[L,mid]$和$b(mid,R]$按照原式的第一种运算方式算,并$FFT$优化即可。
$3.x>y$,那我们只要让$a(mid,R]$和$b[l,mid]$按照原式的第二种运算方式算,并$FFT$优化即可。
伪代码:
solve(L,R) if (L=R) -->1.x=y return -->2.x<y -->3.x>y solve(L,mid),solve(mid+1,R)
代码
#include <bits/stdc++.h> using namespace std; typedef long long LL; const int N=1<<17; double PI=acos(-1.0); int read(){ int x=0; char ch=getchar(); while (!('0'<=ch&&ch<='9')) ch=getchar(); while ('0'<=ch&&ch<='9') x=(x<<3)+(x<<1)+ch-48,ch=getchar(); return x; } int T,n,m,q,a[N],b[N]; LL tot[N]; int s,d,Rev[N]; struct C{ double r,i; C(){} C(double a,double b){r=a,i=b;} C operator + (C x){return C(r+x.r,i+x.i);} C operator - (C x){return C(r-x.r,i-x.i);} C operator * (C x){return C(r*x.r-i*x.i,r*x.i+i*x.r);} }w[N],A[N],B[N]; void FFT(C a[],int n){ for (int i=0;i<n;i++) if (i<Rev[i]) swap(a[i],a[Rev[i]]); for (int t=n>>1,d=1;d<n;d<<=1,t>>=1) for (int i=0;i<n;i+=(d<<1)) for (int j=0;j<d;j++){ C tmp=w[t*j]*a[i+j+d]; a[i+j+d]=a[i+j]-tmp; a[i+j]=a[i+j]+tmp; } } void solve(int L,int R){ if (L==R){ tot[0]+=1LL*a[L]*b[R]; return; } int mid=(L+R)>>1; //a[L]...a[mid] VS b[mid+1]...b[R] for (s=1,d=0;s<R-L;s<<=1,d++); for (int i=0;i<s;i++){ Rev[i]=(Rev[i>>1]>>1)|((i&1)<<(d-1)); w[i]=C(cos(2*i*PI/s),sin(2*i*PI/s)); A[i]=B[i]=C(0,0); } for (int i=L;i<=mid;i++) A[i-L]=C(a[i],0); for (int i=mid+1;i<=R;i++) B[i-mid-1]=C(b[i],0); FFT(A,s),FFT(B,s); for (int i=0;i<s;i++) A[i]=A[i]*B[i],w[i].i*=-1.0; FFT(A,s); for (int i=0;i<R-L;i++) tot[i+L+mid+1]+=(LL)(A[i].r/s+0.5); //a[mid+1]...a[R] VS b[L]...b[mid] for (int i=0;i<s;i++) A[i]=B[i]=C(0,0),w[i].i*=-1.0; for (int i=mid+1;i<=R;i++) A[i-mid-1]=C(a[i],0); for (int i=L;i<=mid;i++) B[mid-i]=C(b[i],0); FFT(A,s),FFT(B,s); for (int i=0;i<s;i++) A[i]=A[i]*B[i],w[i].i*=-1.0; FFT(A,s); for (int i=0;i<R-L;i++) tot[i+mid+1-mid]+=(LL)(A[i].r/s+0.5); solve(L,mid),solve(mid+1,R); } int main(){ T=read(); while (T--){ n=read(),m=read(),q=read(); memset(a,0,sizeof a); memset(b,0,sizeof b); while (n--) a[read()]++; while (m--) b[read()]++; memset(tot,0,sizeof tot); solve(0,50000); while (q--) printf("%lld\n",tot[read()]); } return 0; }