HDU5307 He is Flying(FFT)

本文解析了HDU5307 HeisFlying题目,介绍了如何利用前缀和与多项式乘法的思想解决区间求和问题,并通过快速傅里叶变换(FFT)进行高效计算。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

HDU5307 He is Flying

原题地址http://acm.hdu.edu.cn/showproblem.php?pid=5307

题意:
给定n个非负整数 a1...an a 1 . . . a n ,对所有 0Sai 0 ≤ S ≤ ∑ a i ,输出所有 ai a i 和为S的区间的长度和.

数据范围
1n105 1 ≤ n ≤ 10 5 ai5×104. ∑ a i ≤ 5 × 10 4 .

题解:

si s i ai a i 的前缀和,那么对于区间[i,j],对 sisj1 s i − s j − 1 有着 ji+1 j − i + 1 的贡献。
易于想到的是 xsixsj1=xsisj1 x s i ∗ x − s j − 1 = x s i − s j − 1
由于每个区间的贡献是长度 ij+1 i − j + 1
可以构造出多项式 xsii ×xsi1xsi ×xsi1(i1) ∑ x s i i   × x s i − 1 − ∑ x s i   × x s i − 1 ( i − 1 )
xs x s 的系数就是s的答案。
用FFT计算即可。

1A开心。

代码:

#include<cstdio>
#include<iostream>
#include<cstring>
#include<algorithm>
#include<cmath>
#define LL long long
#define LD long double
const long double Pi=acos(-1);
using namespace std;
const int N=200005;
inline int read()
{
    int ret=0,w=1; char ch=getchar();
    while((ch<'0'||ch>'9')&&ch!='-') ch=getchar();
    if(ch=='-') w=-1,ch=getchar();
    while(ch>='0'&&ch<='9') ret=(ret<<1)+(ret<<3)+ch-'0',ch=getchar();
    return ret*w;
}
int T,n,s[N],R[N],p,len;
LL ans[N];
struct Virt
{
    LD r,i;
    Virt(){}
    Virt(LD r,LD i):r(r),i(i){}
    Virt operator+(const Virt &A){return Virt(r+A.r,i+A.i);}
    Virt operator-(const Virt &A){return Virt(r-A.r,i-A.i);}
    Virt operator*(const Virt &A){return Virt(r*A.r-i*A.i,r*A.i+i*A.r);}
}a[N],b[N],omg[N],_omg[N];
void FFT(Virt *x,int opt)
{
    for(int i=0;i<len;i++) if(i<R[i]) swap(x[i],x[R[i]]);
    Virt *w; if(opt==1) w=omg; else w=_omg;
    for(int m=2;m<=len;m<<=1)
    {
        int l=m>>1;
        for(int j=0;j<len;j+=m)
        for(int i=0;i<l;i++)
        {
            Virt y=w[len/m*i]*x[j+i+l];
            x[j+i+l]=x[j+i]-y;
            x[j+i]=x[j+i]+y;
        }
    }
    if(opt==-1) for(int i=0;i<len;i++) x[i].r/=len;
}
int main()
{
    T=read();
    while(T--)
    {
        n=read(); LL ret=0; LL cnt=0; s[0]=0;
        for(int i=1;i<=n;i++) 
        {
            s[i]=read();
            if(s[i]==0) {cnt++,ret+=1LL*(1LL+cnt)*cnt/2LL;}
            else cnt=0;
            s[i]+=s[i-1];
        }
        printf("%lld\n",ret); 
        for(p=0,len=1;len<=2*s[n];len<<=1,p++);
        for(int i=0;i<len;i++){omg[i]=Virt(cos(2.0*Pi/(LD)len*i),sin(2.0*Pi/(LD)len*i)); _omg[i]=Virt(omg[i].r,-omg[i].i);}
        R[0]=0; for(int i=1;i<len;i++) R[i]=(R[i>>1]>>1)|((i&1)<<(p-1));
        for(int i=0;i<=len;i++) a[i]=b[i]=Virt(0,0);
        for(int i=1;i<=n;i++) {a[s[i]].r+=(LD)i; b[-s[i-1]+s[n]].r+=(LD)1.0;}
        FFT(a,1); FFT(b,1);
        for(int i=0;i<len;i++) a[i]=a[i]*b[i];
        FFT(a,-1);
        for(int i=1;i<=s[n];i++) ans[i]=(LL)(a[i+s[n]].r+0.5);
        for(int i=0;i<=len;i++) a[i]=b[i]=Virt(0,0);
        for(int i=1;i<=n;i++) {a[s[i]].r+=1.0; b[-s[i-1]+s[n]].r+=(LD)(i-1);}
        FFT(a,1); FFT(b,1);
        for(int i=0;i<len;i++) a[i]=a[i]*b[i];
        FFT(a,-1);
        for(int i=1;i<=s[n];i++) ans[i]-=(LL)(a[i+s[n]].r+0.5);
        for(int i=1;i<=s[n];i++) printf("%lld\n",ans[i]);
    }   
    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值