洛谷 P3338 【ZJOI2014】力/BZOJ 3527 力 题解

洛谷 P3338 【ZJOI2014】力/BZOJ 3527 力 题解

题意

给定一个正整数n(1n105)n(1\leq n\leq 10^5),以及有nn个元素的浮点数序列q(0<qi<109)q(0<q_i<10^9).要求对于所有的i[1,n]i\in [1,n],输出

Ej=i=1j1qi×qj(ij)2i=j+1nqi×qj(ij)2E_j=\sum_{i=1}^{j-1}\frac{q_i\times q_j}{(i-j)^2}-\sum_{i=j+1}^n\frac{q_i\times q_j}{(i-j)^2}

注:当你输出的答案与标准答案相差不超过10210^{-2}时,即视为答案正确.

题解

显然,暴力是行不通的,所以考虑使用FFTFFT进行卷积来处理.
首先考虑序列能进行卷积所要满足的条件:

ci=j=0iaj×bnjc_i=\sum_{j=0}^ia_j\times b_{n-j}

所以我们必须要把原式变得好看一点才能进行卷积.
我们不妨令ai=qi,bi=1i2a_i=q_i,b_i=\frac{1}{i^2},那么可以对原式进行一下变形:

Ej=i=1j1qi×qj(ij)2i=j+1nqi×qj(ij)2=i=1j1aibjii=j+1naibijE_j=\sum_{i=1}^{j-1}\frac{q_i\times q_j}{(i-j)^2}-\sum_{i=j+1}^n\frac{q_i\times q_j}{(i-j)^2} =\sum_{i=1}^{j-1}a_ib_{j-i}-\sum_{i=j+1}^{n}a_ib_{i-j}

那么当我们令a0=b0=0a_0=b_0=0时,不难发现将其变形为

i=0jaibjii=jnaibij\sum_{i=0}^{j}a_ib_{j-i}-\sum_{i=j}^{n}a_ib_{i-j}

依然是正确的.此时前一项已经满足卷积的要求了,我们单独处理后一项.
不难证明一下变形这正确的:

i=jnaibij=i=0njai+jbi\sum_{i=j}^{n}a_ib_{i-j}=\sum_{i=0}^{n-j}a_{i+j}b_i

接下来运用反转的思想,令ci=anic_i=a_{n-i},那么可变形为

i=0njai+jbi=i=0njcnijbi\sum_{i=0}^{n-j}a_{i+j}b_i=\sum_{i=0}^{n-j}c_{n-i-j}b_i

这样一来,第二个式子也满足卷积的条件啦!
所以我们对aabb跑一次FFTFFT,再对bbcc跑一次FFTFFT,设得到的两个序列为ppqq,那么答案就为Ej=pjqnjE_j=p_j-q_{n-j},套上FFTFFT的板子即可.

Code

#include<bits/stdc++.h>
using namespace std;
#define M_PI 3.141592653589793
typedef complex<long double> comp;
const int MAXN=100010;
const comp I(0,1);
int n;
comp a[MAXN*4];
comp b[MAXN*4];
inline void change(comp *f,int len)
{
    for(int i=1,j=len>>1;i<len-1;i++)
    {
        if(i<j) swap(f[i],f[j]);
        int k=len>>1;
        while(j>=k)
        {
            j-=k;
            k>>=1;
        }
        if(j<k) j+=k;
    }
}
inline void FFT(comp *f,int len,int rev)
{
    change(f,len);
    for(int h=2;h<=len;h<<=1)
    {
        comp wn=exp(I*(long double)(2*M_PI/h*rev));
        for(int i=0;i<len;i+=h)
        {
            comp cur(1,0);
            for(int j=i;j<i+(h>>1);j++)
            {
                comp p=f[j],q=cur*f[j+(h>>1)];
                f[j]=p+q,f[j+(h>>1)]=p-q;
                cur*=wn;
            }
        }
    }
    if(rev==-1)
    for(int i=0;i<len;i++) f[i]/=len;
}
comp c[MAXN*4];
int len;
int main()
{
    scanf("%d",&n);
    for(int i=0;i<n;i++)
    {
        long double x;
        scanf("%Lf",&x);
        a[i]={x,0};
    }
    for(int i=1;i<n;i++) b[i]=1.0/(1.0*(long double)i*(long double)i);
    for(int i=0;i<n;i++) c[i]=a[n-i];
    len=1;
    while(len<=2*n) len<<=1;
    FFT(a,len,1),FFT(b,len,1),FFT(c,len,1);
    for(int i=0;i<len;i++) c[i]*=b[i],b[i]*=a[i];
    FFT(b,len,-1),FFT(c,len,-1);
    for(int i=0;i<n;i++) printf("%.3Lf\n",real(b[i]-c[n-i]));
	return 0;
}

后记

本人第一次做FFTFFT练习题,感觉还算顺利(除了悄悄地看了两眼题解的解题思路)比较惨的是之前π精度不够结果居然一片红,所有点都差零点几的样子QAQ所幸后面改过来之后一边就过了吧……最后我一定要吐槽一句,数学题的LaTeXLaTeX真难写……搞了半天才搞定QAQ