您的位置:首页 > 其它

HDU5307 He is Flying

2016-06-19 14:41 363 查看
题目链接

题目大意

给定n个非负整数a1~an,对每个0≤S≤∑ai,输出所有和为S的区间的长度和.

1≤n≤105,∑ai≤5×104.

题解

写这题真心涨姿势了…

官方题解构造了这样一个多项式:

(∑ixsi)(∑x−si−1)−(∑xsi)(∑(i−1)x−si−1).

其中si=∑ij=1aj(1≤i≤n),s0=0.

然后得到的多项式的xS(1≤S≤∑ai)的系数即为所求的S对应的答案.

一开始看到这个我完全是不知所措的,觉得题解真是用心险恶.

然而这个其实还是好理解的,区间[i,j]的和为S,很容易想到化成sj−si−1=S.而上面的多项式左边两项乘起来是这样:

ixsi⋅x−sj−1=ixsi−sj−1.

右边的两项乘起来是这样:

xsi⋅(j−1)x−sj−1=(j−1)xsi−sj−1.

两个相减就得到了这么个东西:

(i−j+1)xsi−sj−1.

于是十分巧妙地把S体现在了指数上,把要求的区间长度体现在了系数上.

而且这样也避免了遗漏和重复计算,除了S=0的情况,因为S=0可以由si−si得到,也可以由两个相等的前缀和si=sj通过si−sj和sj−si两次计算得到,这样会比较麻烦.而实际上直接在原数组ai上O(n)扫一遍就可以得出S=0的答案了,所以可以另外考虑.

所以接下来只要算那个构造出来的多项式就好.这个可以用FFT,但是由于指数会有负的,我们把指数为负的先加上一个值(比如sn),然后就可以直接用FFT了.

说起来很简单,但是实现还是有点小烦的.

还有这题会卡精度所以要用long double.

总的时间复杂度是O(nlgn).

后来手写了个复数竟然快了近三倍orz…

说写这题涨姿势是因为调试时发现long double要用%Ld输出…

代码

#include<cstdio>
#include<cmath>
#include<cstring>
#include<iostream>
#include<algorithm>
#define debug(x) cout<<#x<<"="<<x<<endl
using namespace std;
typedef long long ll;
typedef long double ld;
const int N=1e5+5,M=(1<<17)+5;
const ld pi=acos(-1.0);
int sum
,rev[M];
struct C{
ld real,imag;
C(ld real=0,ld imag=0):real(real),imag(imag){}
friend C operator +(C A,C B){
return C(A.real+B.real,A.imag+B.imag);
}
friend C operator -(C A,C B){
return C(A.real-B.real,A.imag-B.imag);
}
friend C operator *(C A,C B){
return C(A.real*B.real-A.imag*B.imag,A.imag*B.real+A.real*B.imag);
}
}A[M],B[M],ans[2][M];
void rd(int &res){
res=0;
char c;
while(c=getchar(),c<48);
do res=(res<<3)+(res<<1)+(c^48);
while(c=getchar(),c>47);
}
void FFT(C *arr,int n,int flag){
for(int i=0;i<n;++i)
if(i<rev[i])
swap(arr[i],arr[rev[i]]);
for(int m=2;m<=n;m<<=1){
C wm(cos(2*pi/m),flag*sin(2*pi/m));
for(int i=0;i<n;i+=m){
C w(1,0);
for(int j=0;j<m>>1;++j,w=w*wm){
C x=arr[i+j],y=w*arr[i+j+(m>>1)];
arr[i+j]=x+y;
arr[i+j+(m>>1)]=x-y;
}
}
}
}
void calc_FFT(int n,bool id){
int _n=n,m=n<<1,S=0;
for(n=1;n<=m;n<<=1,++S);
rev[0]=0;
for(int i=1;i<n;++i)
rev[i]=(rev[i>>1]>>1)|((i&1)<<(S-1));
for(int i=_n+1;i<n;++i)
A[i]=B[i]=0;
FFT(A,n,1);
FFT(B,n,1);
for(int i=0;i<n;++i)
ans[id][i]=A[i]*B[i];
FFT(ans[id],n,-1);
for(int i=0;i<=m;++i)
ans[id][i].real/=n;
}
void solve(){
int n;
ll ans0=0;
rd(n);
sum[0]=0;
for(int i=1,num,cnt=0;i<=n;++i){
rd(num);
sum[i]=sum[i-1]+num;
if(!num){
++cnt;
ans0+=1ll*cnt*(cnt+1)/2;
}
else cnt=0;
}
printf("%I64d\n",ans0);
for(int i=0;i<=sum
;++i){
A[i]=B[i]=0;
}
for(int i=1;i<=n;++i){
A[sum[i]].real+=i;
B[-sum[i-1]+sum
].real+=1;
}
calc_FFT(sum
,0);
for(int i=0;i<=sum
;++i){
A[i]=B[i]=0;
}
for(int i=1;i<=n;++i){
A[sum[i]].real+=1;
B[-sum[i-1]+sum
].real+=i-1;
}
calc_FFT(sum
,1);
for(int i=1;i<=sum
;++i){
printf("%I64d\n",(ll)(ans[0][i+sum
].real-ans[1][i+sum
].real+0.5));
}
}
int main(){
int cas;
rd(cas);
while(cas--)solve();
return 0;
}
/*

Jun.19.16

Tags:math,FFT
Submissions:5

Exe.Time 1107MS
Exe.Memory 18880K
Code Len. 2239B

*/
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签:  HDU FFT 构造