多项式多点求值的转置做法
对于 \(n\) 维向量 \(f\) 作一系列线性运算,也就是下列三种操作:
- \(f_i = f_i + c \times f_j\\\)
- \(f_i = f_i \times c\)
- \(\operatorname{swap}(f_i,f_j)\)
(其中 \(c\) 是与 \(f\) 无关的常数。)
(上面那三种线性运算都可以写成一个简单的 \(n \times n\) 的矩阵(初等矩阵)左乘 \(f\)。)那么因为矩阵有结合律,对 \(f\) 作一系列线性运算就相当于把每一步的线性运算对应的矩阵相乘的结果—— 称其为 \(V\) ——左乘上 \(f\) 。
转置原理:
若 \(V\) 已确定,则 \(f \to V^T f\) 的计算过程可以推出 \(f \to V f\) 的计算过程,且运算次数 完 全 一 致。
考虑将 \(V^T\) 拆成若干个初等矩阵的乘积:
那么 \(f \to V^T f\) 的计算过程就是依次对 \(f\) 施加 \(E_1,E_2...E_k\) 所对应的线性运算的过程。
而根据转置的性质 \((AB)^T = B^T A^T\) 可以得到:
那么 \(f \to V f\) 的计算过程就是依次对 \(f\) 施加 \(E_k^T...E_2^T,E_1^T\) 所对应的线性运算的过程。
(线性运算转置后,操作 2,3 不变,操作 1 变为 \(f_j = f_j + c \times f_i\))
若我们有 \(S=\{S_1,S_2,..S_n\}\) (\(S\) 中元素两两不同)和一个 \(n-1\) 次多项式 \(f\)
构造范德蒙德矩阵 \(V\):
并将 \(f\) 写成向量形式:
那么多点求值所求即为 \(V \times f\) 。\(V\) 与 \(f\) 无关,可以视作确定的。
考虑 \(V^T \times f\) :
也就是 \(n\) 维向量 \(g\) 满足
那么转化为求多项式 \(\sum \dfrac{f_i}{1-S_ix}\) ,这个东西可以分治处理。
分母与 \(f\) 无关(已确定),先分治预处理掉;然后分治求分子。这部分复杂度 \(O(n\log^2n)\):
- 把 \(f\) 通过一系列 \(\operatorname{swap}\) 操作放到分治求分子的叶子部分
- 分治求分子
- 乘上分母的逆元得到 \(V^T \times F\)
接下来根据转置原理就可以得到 \(V \times f\) 了;我们可以手动转置,把上面那个分治求分子的所有线性变换离线下来,然后顺序反过来做并且转置掉。但是这显然是非常让人愤怒的。
我们不妨分析转置后的性质:考虑卷积的转置是什么。
对于 \(f,g\) ,其中 \(g\) 已确定,\(f \times g = h\) ,则
转置后,
那这个也能用 \(\operatorname{NTT}\) 处理出来。将这个转置后的卷积称为 \(\times^T\) 。所以原来的
转置成
( \(u_l,u_r\) 的长度应保留至与 \(V \times f\) 过程中 \(u_l,u_r\) 的长度相同)
这部分复杂度也是 \(O(n\log^2n)\) :
- 将 \(f \times^T\) 分母的逆元
- 往下递归
- 将分治中叶子处的信息通过一系列 \(\operatorname{swap}\) 操作放到 \(V\times f\) 上
namespace Multi_Eval{
#define ls(x) (x << 1)
#define rs(x) (x << 1 | 1)
poly gate[maxn<<2] ;
void init(int now,int l,int r,int *Alpha){
if(l == r) return gate[now] = poly({1,mod-Alpha[l]}),void() ;
int mid = (l+r) >> 1 ;
init(ls(now),l,mid,Alpha) , init(rs(now),mid+1,r,Alpha) ;
gate[now] = (gate[ls(now)]*gate[rs(now)]).resize(r-l+2) ;
}
poly Gate[maxn<<2] ;
//Alpha -> f , Beta -> S{x}
void evaluation(int now,int l,int r,int *Zeta){
if(l == r) return Zeta[l] = Gate[now][0],void() ;
int mid = (l+r) >> 1 ;
Gate[rs(now)] = (Gate[now].copy().reverse()*gate[ls(now)]).resize(Gate[now].size()).reverse().resize(r - mid) ;
Gate[ls(now)] = (Gate[now].copy().reverse()*gate[rs(now)]).resize(Gate[now].size()).reverse().resize(mid-l+1) ;
evaluation(ls(now),l,mid,Zeta),evaluation(rs(now),mid+1,r,Zeta) ;
}
void Evaluation(poly Alpha,int *Beta,int n,int *Zeta){
n = max(static_cast<int>(Alpha.size()),n) ;init(1,1,n,Beta),Alpha.resize(n) ;
Gate[1] = (Alpha.reverse()*(gate[1].inv())).resize(Alpha.size()).reverse() ; // Alpha is reversed after this
evaluation(1,1,n,Zeta) ;
}
#undef ls
#undef rs
}