CF993E
题意:
给你一个数组 $a_{1 \sim n}$,对于 $k = 0 \sim n$,求出有多少个数组上的区间满足:区间内恰好有 $k$ 个数比 $x$ 小。$x$ 为一个给定的数。
$n \le 2 \times 10^5$。值域没有意义。
分析:
对于$a_{i}$,若$a_{i}<x$则$a_{i}=1$,反之$a_{i}=0$。
设$s$为$a$的前缀和,$c$数组为统计的数组,即求:
$$
\begin{align}
&\sum_{i=k}^{n}s_{i}s_{i-k}\\
&=\sum_{i=k}^{n}s_{i}g_{n-i+k}\\
&=\sum_{i+j=n+k}s_{i}g_{j}
\end{align}
$$
设$g_{j}=c_{n-i}$,再求卷积即可
关于反转技巧,可以看这
关于$ans_{0}$,从实际意义上考虑,是$0$串的个数,即所有$s_{i}=s_{j}$(之类的)的情况
不想再打式子了,此时会有$i=j$的情况,共$n+1$组,其余由于相等会不分顺序算$2$次,所以要先减再除以$2$。
本题答案很大,不可以$ntt$,只可用$fft$
code:
#include<bits/stdc++.h> #define ll long long using namespace std; const int N=1<<22; const double pi=acos(-1); struct comp{ double x,y; comp(double xx=0,double yy=0){ x=xx,y=yy; } }; comp operator +(comp a,comp b){ return comp(a.x+b.x,a.y+b.y); } comp operator -(comp a,comp b){ return comp(a.x-b.x,a.y-b.y); } comp operator *(comp a,comp b){ return comp(a.x*b.x-a.y*b.y,a.x*b.y+a.y*b.x); } int r[N],lim,L,n,m,a[N]; comp c[N],d[N]; void fft(comp *f,int fg){ for(int i=0;i<lim;i++){ if(i<r[i]){ swap(f[i],f[r[i]]); } } for(int p=2;p<=lim;p<<=1){ int len=p>>1; comp buf(cos(pi/len),fg*sin(pi/len)); for(int j=0;j<lim;j+=p){ comp t(1,0); for(int k=j;k<j+len;k++){ comp w=t*f[k+len]; f[k+len]=f[k]-w; f[k]=f[k]+w; t=t*buf; } } } } int main(){ cin>>n>>m; for(int i=1;i<=n;i++){ int x; scanf("%d",&x); if(x<m) a[i]=1; a[i]+=a[i-1]; } for(int i=0;i<=n;i++){ c[a[i]].x++; d[n-a[i]]=c[a[i]]; } while(1<<L<=2*n)L+=1; lim=1<<L; for(int i=0;i<lim;i++){ r[i]=(r[i>>1]>>1)|((i&1)<<(L-1)); } fft(c,1); fft(d,1); for(int i=0;i<lim;i++){ c[i]=c[i]*d[i]; } fft(c,-1); for(int i=0;i<=n;i++){ if(i==0){ printf("%.0lf ",(c[i+n].x/lim-n-1)/2+0.3); } else printf("%.0lf ",(c[i+n].x)/lim+0.3); } puts(""); return 0; }