前置知识

(以下内容并不严谨,可以参考论文《转置原理的简单介绍》)

对于一个算法,其为线性算法当且仅当仅包含以下操作:

1.$read\ i$,将$r_{i}$的值赋为(下一个)读入的元素

2.$write\ i$,将$r_{i}$的值赋给(下一个)输出的元素

3.$update\ i\ j\ c$,将$r_{i}$的值修改为$r_{i}+c\cdot r_{j}$(其中$c$为常数)

其中$r_{i}$表示第$i$个变量,初值均为0

由此,线性算法即可用一个操作序列描述

假设有$n$次$read$操作和$m$次$write$操作,不妨强制这$n$次$read$和$m$次$write$操作分别严格在$update$之前和之后(以下$n,m$意义默认为此意义,且保证此性质)

结论1:对于线性算法,恰存在一个$m\times n$的矩阵$A$——若将读入的$n$个元素和输出的$m$个元素分别构成$n\times 1$和$m\times 1$的矩阵$x$和$y$,则$y=Ax$

归纳每一个变量都是$x_{i}$的线性组合即可

进一步的,称该线性算法为矩阵$A$的线性算法

结论2:对于矩阵$A$的线性算法,将其操作序列翻转并将操作依次变为$write\ i,read\ i$和$update\ j\ i\ c$,则得到的线性算法为$A^{T}$的线性算法

操作可以看作不断乘上矩阵$E_{1},E_{2},...,E_{t}$,根据定义$A=\prod_{i=1}^{t}E_{i}$

操作翻转和变化即变为乘上$E_{t}^{T},E_{t-1}^{T},...,E_{1}^{T}$,而注意到$A^{T}=\prod_{i=t}^{1}E_{i}^{T}$,也即成立

题解

令$f_{k}(x)=\prod_{i=1}^{k}(x+b_{i})$,则问题即求$\sum_{i=1}^{n}c_{i}f_{k}(a_{i})$

将其以多项式的形式展开,即$\sum_{i=1}^{n}c_{i}\sum_{j=0}^{n}[x^{j}]f_{k}(x)\cdot a_{i}^{j}$

调换枚举顺序,即$\sum_{j=0}^{n}[x^{j}]f_{k}(x)\sum_{i=1}^{n}c_{i}a_{i}^{j}$

记后者为$S_{j}$,注意到其可以看作$\sum_{i=1}^{n}c_{i}\cdot [x^{j}]\frac{1}{1-a_{i}x}=[x^{j}]\sum_{i=1}^{n}\frac{c_{i}}{1-a_{i}x}$,那么考虑求该多项式,可以对其分治计算,并以分式的形式存储即可(最后再求逆展开)

由此,问题即求$\sum_{i=0}^{n}S_{i}\cdot [x^{i}]f_{k}(x)$

构造矩阵$A_{i,k}=[x^{i}]f_{k}(x)$,并将$S$看作输入矩阵(预处理过程显然独立),那么即需要实现$A$的线性算法,根据转置原理(结论2)不妨去实现$A^{T}$的线性算法

关于$A^{T}$的线性算法,假设读入为$Q$,代入式子即求$\sum_{i=1}^{n}Q_{i}A_{k,i}=[x^{k}]\sum_{i=1}^{n}Q_{i}\prod_{j=1}^{i}(x+b_{j})$

分治,并求出$H(x)=\prod_{j=l}^{r}(x+b_{j})$和$F(x)=\sum_{i=l}^{r}Q_{i}\prod_{j=l}^{i}(x+b_{j})$,则转移如下
$$
\begin{cases}H(x)=H_{l}(x)H_{r}(x)\\F(x)=F_{l}(x)+H_{l}(x)F_{r}(x)\end{cases}
$$
进而$A$的线性算法即将过程"转置",具体做法如下——

$H(x)$与输入$S$无关,因此可以先预处理出来

$A^{T}$的线性算法中,最后执行的是输出$[x^{i}]F(x)$,那么即需要读入$[x^{i}]F(x)$(注意要求$AS$,即读入为$S$)

分治的形式是从底向上做,那么反过来即要自顶向下做(先执行操作再递归)

下面,来考虑操作的翻转,从后往前依次考虑操作:

1.$[x^{i}]F(x)=[x^{i}]F_{l}(x)+[x^{i}]G(x)$(其中$G(x)=H_{l}(x)F_{r}(x)$),将其表示为形如$update\ i\ j\ c$的操作,再变换后也即$[x^{i}]F_{l}(x)=[x^{i}]G(x)=[x^{i}]F(x)$

2.$ntt(G,-1),[x^{i}]G(x)=[x^{i}]H_{l}(x)\cdot [x^{i}]F_{r}(x),ntt(F_{r},1)$

(注意这只是将整体的顺序调过来,内部并没有翻转)

关于$ntt$内部如何转置,注意到$ntt(a,p)$可以看作将$a$乘上矩阵$A_{i,j}=\omega^{pij}$,联系结论2的证明即需要将该矩阵转置,而显然转置后仍为自身,因此不需要变化

(但注意$ntt(*,-1)$中的除法虽然不在矩阵乘法的范围内,但不取出处理也是正确的)

接下来即仅有第2步,注意到其中$[x^{i}]H_{l}(x)$看作是常数,因此需要先对其ntt(可以看作预处理),因此也即变为$[x^{i}]F_{r}(x)=[x^{i}]H_{l}(x)\cdot [x^{i}]G(x)$

$A^{T}$的线性算法中,最先执行的是将读入$[x^{1}]F(x)=S_{i}$和$[x^{0}]F(x)=b_{i}\cdot [x^{1}]F(x)$,那么将其转置后即需输出$b_{i}\cdot [x^{0}]F(x)+[x^{1}]F(x)$

时间复杂度为$o(n\log^{2}n)$,可以通过

  1 #include<bits/stdc++.h>
2 using namespace std;
3 #define N 250005
4 #define mod 998244353
5 #define ll long long
6 #define vi vector<int>
7 #define L (k<<1)
8 #define R (L+1)
9 #define mid (l+r>>1)
10 int n,a[N],b[N],c[N],rev[1<<20];
11 vi A[N<<2],B[N<<2],F[N<<2];
12 int Log(int m){
13 int n=1;
14 while (n<m)n<<=1;
15 return n;
16 }
17 int qpow(int n,int m){
18 int s=n,ans=1;
19 while (m){
20 if (m&1)ans=(ll)ans*s%mod;
21 s=(ll)s*s%mod;
22 m>>=1;
23 }
24 return ans;
25 }
26 void init(int n){
27 for(int i=0;i<n;i++)rev[i]=(rev[i>>1]>>1)+((i&1)*(n>>1));
28 }
29 void ntt(vi &a,int n,int p=0){
30 a.resize(n);
31 for(int i=0;i<n;i++)
32 if (i<rev[i])swap(a[i],a[rev[i]]);
33 for(int i=2;i<=n;i<<=1){
34 int s=qpow(3,(mod-1)/i);
35 if (p)s=qpow(s,mod-2);
36 for(int j=0;j<n;j+=i)
37 for(int k=0,ss=1;k<(i>>1);k++,ss=(ll)ss*s%mod){
38 int x=a[j+k],y=(ll)ss*a[j+k+(i>>1)]%mod;
39 a[j+k]=(x+y)%mod,a[j+k+(i>>1)]=(x-y+mod)%mod;
40 }
41 }
42 if (p){
43 int s=qpow(n,mod-2);
44 for(int i=0;i<n;i++)a[i]=(ll)a[i]*s%mod;
45 }
46 }
47 vi mul(vi a,vi b,int m){
48 int n=Log(m<<1);
49 if (m<0)m=a.size()+b.size()-1,n=Log(m);
50 init(n);
51 for(int i=m;i<a.size();i++)a[i]=0;
52 for(int i=m;i<b.size();i++)b[i]=0;
53 ntt(a,n),ntt(b,n);
54 vi ans;
55 for(int i=0;i<n;i++)ans.push_back((ll)a[i]*b[i]%mod);
56 ntt(ans,n,1);
57 for(int i=m;i<n;i++)ans.pop_back();
58 return ans;
59 }
60 vi inv(vi a,int n){
61 vi ans;
62 if (n==1){
63 ans.push_back(qpow(a[0],mod-2));
64 return ans;
65 }
66 vi s=inv(a,(n>>1));
67 ans=mul(a,s,n);
68 for(int i=0;i<n;i++)ans[i]=mod-ans[i];
69 ans[0]+=2;
70 return mul(s,ans,n);
71 }
72 void get_S(int k,int l,int r){
73 if (l==r){
74 A[k].push_back(c[l]);
75 B[k].push_back(1),B[k].push_back(mod-a[l]);
76 return;
77 }
78 get_S(L,l,mid),get_S(R,mid+1,r);
79 int m=B[L].size()+B[R].size()-1,n=Log(m);
80 init(n);
81 ntt(A[L],n),ntt(A[R],n),ntt(B[L],n),ntt(B[R],n);
82 for(int i=0;i<n;i++){
83 A[k].push_back(((ll)A[L][i]*B[R][i]+(ll)A[R][i]*B[L][i])%mod);
84 B[k].push_back((ll)B[L][i]*B[R][i]%mod);
85 }
86 ntt(A[k],n,1),ntt(B[k],n,1);
87 for(int i=m;i<n;i++){
88 A[k].pop_back();
89 B[k].pop_back();
90 }
91 }
92 void get_H(int k,int l,int r){
93 if (l==r){
94 B[k].clear();
95 B[k].push_back(b[l]),B[k].push_back(1);
96 return;
97 }
98 get_H(L,l,mid),get_H(R,mid+1,r);
99 B[k]=mul(B[L],B[R],-1);
100 }
101 void get_F(int k,int l,int r){
102 if (l==r){
103 printf("%d\n",((ll)b[l]*F[k][0]+F[k][1])%mod);
104 return;
105 }
106 F[L]=F[R]=F[k];
107 int m=B[L].size(),n=F[L].size();
108 for(int i=m;i<n;i++)F[L].pop_back();
109 m+=B[R].size()-1,n=Log(m);
110 init(n);
111 ntt(B[L],n),ntt(F[R],n,1);
112 for(int i=0;i<n;i++)F[R][i]=(ll)F[R][i]*B[L][i]%mod;
113 ntt(F[R],n);
114 m=B[R].size();
115 for(int i=m;i<n;i++)F[R].pop_back();
116 get_F(L,l,mid),get_F(R,mid+1,r);
117 }
118 int main(){
119 scanf("%d",&n);
120 for(int i=1;i<=n;i++)scanf("%d",&a[i]);
121 for(int i=1;i<=n;i++)scanf("%d",&b[i]);
122 for(int i=1;i<=n;i++)scanf("%d",&c[i]);
123 get_S(1,1,n);
124 A[1]=mul(A[1],inv(B[1],Log(n+1)),n+1);
125 get_H(1,1,n);
126 F[1]=A[1],get_F(1,1,n);
127 return 0;
128 }

[gym102978D]Do Use FFT的更多相关文章

  1. 并行计算提升32K*32K点(32位浮点数) FFT计算速度(4核八线程E3处理器)

    对32K*32K的随机数矩阵进行FFT变换,数的格式是32位浮点数.将产生的数据存放在堆上,对每一行数据进行N=32K的FFT,记录32K次fft的时间. 比较串行for循环和并行for循环的运行时间 ...

  2. 【BZOJ-2179&2194】FFT快速傅里叶&快速傅里叶之二 FFT

    2179: FFT快速傅立叶 Time Limit: 10 Sec  Memory Limit: 259 MBSubmit: 2978  Solved: 1523[Submit][Status][Di ...

  3. 为什么FFT时域补0后,经FFT变换就是频域进行内插?

    应该这样来理解这个问题: 补0后的DFT(FFT是DFT的快速算法),实际上公式并没变,变化的只是频域项(如:补0前FFT计算得到的是m*2*pi/M处的频域值, 而补0后得到的是n*2*pi/N处的 ...

  4. FFT NNT

    算算劳资已经多久没学新算法了,又要重新开始学辣.直接扔板子,跑...话说FFT算法导论里讲的真不错,去看下就懂了. //FFT#include <cstdio> #include < ...

  5. CC countari & 分块+FFT

    题意: 求一个序列中顺序的长度为3的等差数列. SOL: 对于这种计数问题都是用个数的卷积来进行统计.然而对于这个题有顺序的限制,不好直接统计,于是竟然可以分块?惊为天人... 考虑分块以后的序列: ...

  6. ECF R9(632E) & FFT

    Description: 上一篇blog. Solution: 同样我们可以用fft来做...就像上次写的那道3-idoit一样,对a做k次卷积就好了. 同样有许多需要注意的地方:我们只是判断可行性, ...

  7. fft练习

    数学相关一直都好弱啊>_< 窝这个月要补一补数学啦, 先从基础的fft补起吧! 现在做了 道. 窝的fft 模板 (bzoj 2179) #include <iostream> ...

  8. FFT时域与频域的关系,以及采样速率与采样点的影响

    首先对于FFT来说,输入的信号是一个按一定采样频率获得的信号序列,而输出是每个采样点对应的频率的幅度(能量). 下面详细分析: 在FFT的输出数据中,第一个值是直流分量的振幅(这样对应周期有无穷的可能 ...

  9. 【玩转单片机系列002】 如何使用STM32提供的DSP库进行FFT

    前些日子,因为需要在STM32F103系列处理器上,对采集的音频信号进行FFT,所以花了一些时间来研究如何高效并精确的在STM32F103系列处理器上实现FFT.在网上找了很多这方面的资料做实验并进行 ...

随机推荐

  1. 低代码BPM平台

    为了做出明智的决策并为客户提供服务,员工需要在正确的环境中使用正确的工具和访问关键信息的权限.但是,当业务关键信息分散在多个现成的和自定义编码的应用程序中时,员工效率会降低,客户体验也会受到影响. 低 ...

  2. k8s学习笔记(1)- 简单部署springboot应用

    前言:k8s全称kubernetes,k8s是为容器服务而生的一个可移植容器的编排管理工具,越来越多的公司正在拥抱k8s,并且当前k8s已经主导了云业务流程,关于更多的k8s知识,可自行学习 1.k8 ...

  3. Linux中使用dd制作文件的.img

    dd if=/dev/zero of=new_img.img bs=1M count=20   //生成20M的文件,bs块的大小,count块的数量 mkfs.ext3 new_img.img  / ...

  4. Unity——伤害数字显示HUD

    伤害数字显示HUD 游戏中收到伤害掉血,会有飘动的伤害数值: 可以使用OnGUI中GUI.Label来实现: 可自定义字体,颜色,大小等: 如果需要更好看的数字特效,可以手动添加: 普通字体不够好看可 ...

  5. OO_JAVA_JML系列第三次作业__架构之谈

    OO_JAVA_JML系列第三次作业 ## ----架构之谈 目录 OO_JAVA_JML系列第三次作业 出发点 操作的可分离性 操作本身的多样性 实现手段:表驱动编程 储存 注册 出发点 操作的可分 ...

  6. Mac上安装Grafana

    Mac上安装Grafana 一.背景 二.安装步骤 1.通过 Home Brew 安装 2.通过二进制包进行安装 1.下载 2.grafana配置文件的路径 3.修改grafana配置 1.修改默认的 ...

  7. 基于RequestBodyAdvice和ResponseBodyAdvice来实现spring中参数的加密和解密

    在日常开发中,有时候我们经常需要和第三方接口打交道,有时候是我们调用别人的第三方接口,有时候是别人在调用我们的第三方接口,那么为了调用接口的安全性,一般都会对传输的数据进行加密操作,如果每个接口都由我 ...

  8. (五)、Docker 容器数据卷

    1.什么是数据卷 将运用与运行的环境打包形成容器运行 ,运行可以伴随着容器,但是我们对数据的要求希望是持久化的 容器之间希望有可能共享数据 Docker容器产生的数据,如果不通过docker comm ...

  9. pyqgis环境配置

    配置pyqgis开发环境时,很多网上教程写的非常繁琐,这里仅仅找了一个最简单的配置方法,使用pycharm的IDE,安装QGIS软件后,在pycharm的ProjectInterpreter里面填写Q ...

  10. 计算机网络传输层之TCP可靠传输

    文章转自:https://blog.csdn.net/weixin_43914604/article/details/105524592 学习课程:<2019王道考研计算机网络> 学习目的 ...