一、开头
( WC2019 神犇协会)
undefeatedKO : NOI2017 的题大家都 AK 了吗?
All : AK 了!
ION :我们穿越到 2019 年的 WC 怎么样?
olis :好啊!听说一个弱鸡 xyz32768 要来 WC ,我们一到就把他 D 一遍,这样他 WC2019 不爆零才怪呢!
( WC2019 )
VFN :我们刚刚 A 掉了分身术这题。考虑到你比较菜,我就不用这道题考你了,我换成 Day1 T3 的泳池,如果你做不出来,你就肯定会在 WC2019 我们精心出的试题面前爆零!
xyz32768 :什么??????
NFV :哈哈,没想到你这么菜呀,那我再降低下要求:我告诉你这道题的算法是:常系数线性递推。
xyz32768 :什么?矩乘快速幂??????
phantom : 你这个弱鸡居然连多项式整除和取模都不会,明天你爆零定了!再见!
xyz32768 : 算了,爆零就爆零吧,反正我永远都学不会任何多项式算法。
pool : 没想到 xyz32768 你菜得超出我的眼界了,再见,爆零蒟蒻!
二、前置芝士:多项式求逆
三、多项式的整除与取模
一个
次多项式
和
次多项式
,求多项式
和
,满足:
(1)
次数为
,
次数为
。
(2)
这就是求
为
与
整除得到的多项式,且
。
下面进入推式子环节。
两边同乘
:
其中
表示
的系数翻转,即
的
次项系数为
的
次项系数。
需要求
模
的逆。
至此,我们得到了整除的结果。
取模则更简单:
多项式取模的重要应用:如果在一定的条件下
为
,那么将计算
改为计算
有时可以有效地降低复杂度。
四、应用:多项式多点求值
给定一个
次多项式
和
个值
,求出
,
,
,
。
采用分治的算法。取
。
先计算
,
。
那么:
(1)对于任意的
:
(2)对于任意的
:
所以可以分别将
转换成
和
。
所以,设
为计算
取集合
内每个数时多项式的值:
可以分治 FFT 求得。
复杂度有:
由主定理得:
五、应用:多项式快速插值
给定
,求一个多项式
使得对于任何一个
都有:
众所周知,通过 Lagrange 拉格朗日插值公式:
可以得到
的算法。
考虑把 Lagrange 插值的式子转化一下:
设
(可以用分治 FFT 求出)。
记为
。
求导:
取
,得:
于是转化成了多点求值。
于是转化成:
可以用分治 FFT 求得。
具体地,设
,
,
。
那么设
,则上式为:
递归即可。
复杂度和多点求值一样是
。
神仙 zzq 的博客:
orz zzq
六、应用:常系数线性递推
一、引入
地球上的 OIer 都知道 Fibonacci 数列:
但
比较小时可以递推。
当
规模比较大,如
甚至
时,可以利用矩阵乘法优化。
而假设由一个
阶递推数列满足:
和
是已知的。
这是一个
阶的递推数列。
如果暴力转移,则复杂度
。
如果使用矩阵乘法,则复杂度
。
而
且
时,
就需要用到一个线性代数黑科技——常系数线性递推。
二、求法
假设转移矩阵为
,初始列向量:
则:
假设我们构造了一个序列
使得:
那么两边同乘
可得:
三、构造序列
考虑
的生成函数
,即
是一个以矩阵为参数的多项式。
假设有
其中
的次数为
。
如果
,那么就有:
用快速幂+多项式取模就能做到
的复杂度。
为矩阵
的特征多项式。
四、特征值和特征向量
以下把全
的矩阵简写为
。
如果存在数
和列向量
满足:
即
(
为单位矩阵)
那么
和
分别为矩阵
的特征值和特征向量。
一个
阶的满秩矩阵有
组特征值和特征向量。
五、Cayley-Hamilton( C-H )定理.
定理:
其中
为
的第
个特征值。
为了证明这个定理,我们不妨先证明上式乘上任意特征向量等于
矩阵。
先证明:
然后我们能证明乘上
为
矩阵:
于是我们证明了 C-H 定理。
六、求特征多项式
一个奇怪的结论:
(
为矩阵
的行列式)
和
的系数相同。
换句话说,
就是矩阵
的特征多项式
。
你可能会问:
的参数和
的参数不一样,为什么系数会相同?
先假设
的参数不是矩阵而是数。而单位矩阵相当于
,所以先将其视为
。
我们可以发现,将
个特征值
代入这两个多项式都会使多项式的值为
,并且这两个多项式的
项相同。
有了这
个条件,我们得出这两个多项式的系数相同。
而
实际上可以忽略(因为我们只关心
是否为
矩阵,而
矩阵取反后还是
矩阵)。
考虑矩阵
:
手算行列式得到:
所以我们得出特征多项式:
开头中提到的 NOI2017 Day1 T3 泳池 就是一个常系数线性递推。
虽然那题多项式暴力取模能过
七、模板
多项式多点求值和快速插值代码暂无,见谅。
一、多项式整除与取模
#include <cmath>
#include <cstdio>
#include <cstring>
#include <iostream>
#include <algorithm>
#define For(i, a, b) for (i = a; i <= b; i++)
#define Step(i, a, b, x) for (i = a; i <= b; i += x)
#define Pow(k, n) for (k = 1; k < n; k <<= 1)
using namespace std;
inline int read() {
int res = 0; bool bo = 0; char c;
while (((c = getchar()) < '0' || c > '9') && c != '-');
if (c == '-') bo = 1; else res = c - 48;
while ((c = getchar()) >= '0' && c <= '9')
res = (res << 3) + (res << 1) + (c - 48);
return bo ? ~res + 1 : res;
}
const int N = 7e5 + 5, ZZQ = 998244353;
int n, m, tot = 2, f[N], g[N], rf[N], rg[N], invg[N], rev[N],
tmp1[N], tmp2[N], gp[N], ff, quot[N], xmod[N];
int qpow(int a, int b) {
int res = 1;
while (b) {
if (b & 1) res = 1ll * res * a % ZZQ;
a = 1ll * a * a % ZZQ;
b >>= 1;
}
return res;
}
void FFT(int n, int *a, int op) {
int i, j, k, sp = n;
For (i, 0, n - 1) if (i < rev[i]) swap(a[i], a[rev[i]]);
gp[n] = qpow(op == 1 ? 3 : 332748118, (ZZQ - 1) / n);
For (i, 1, tot) sp >>= 1, gp[sp] =
1ll * gp[sp << 1] * gp[sp << 1] % ZZQ;
Pow(k, n) {
int x = gp[k << 1];
Step (i, 0, n - 1, k << 1) {
int w = 1;
For (j, 0, k - 1) {
int u = a[i + j], v = 1ll * w * a[i + j + k] % ZZQ;
a[i + j] = (u + v) % ZZQ;
a[i + j + k] = (u - v + ZZQ) % ZZQ;
w = 1ll * w * x % ZZQ;
}
}
}
}
int main() {
int i, k, orz;
n = read(); m = read();
For (i, 0, n) f[i] = rf[n - i] = read();
For (i, 0, m) g[i] = rg[m - i] = read();
invg[0] = qpow(rg[0], ZZQ - 2);
Pow(k, n - m + 1) {
orz = qpow(k << 2, ZZQ - 2);
For (i, 0, (k << 2) - 1)
rev[i] = (rev[i >> 1] >> 1) | ((i & 1) << tot - 1);
For (i, 0, (k << 1) - 1) tmp1[i] = rg[i];
For (i, (k << 1), (k << 2) - 1) tmp1[i] = 0;
For (i, 0, k - 1) tmp2[i] = invg[i];
For (i, k, (k << 2) - 1) tmp2[i] = 0;
FFT(k << 2, tmp1, 1); FFT(k << 2, tmp2, 1);
For (i, 0, (k << 2) - 1)
tmp1[i] = 1ll * tmp1[i] * tmp2[i] % ZZQ;
For (i, 0, (k << 2) - 1)
tmp1[i] = (2 - tmp1[i] + ZZQ) % ZZQ;
For (i, 0, k - 1) tmp2[i] = invg[i];
For (i, k, (k << 2) - 1) tmp2[i] = 0;
FFT(k << 2, tmp2, 1);
For (i, 0, (k << 2) - 1)
invg[i] = 1ll * tmp1[i] * tmp2[i] % ZZQ;
FFT(k << 2, invg, -1);
For (i, 0, (k << 2) - 1) invg[i] = 1ll * invg[i] * orz % ZZQ;
For (i, (k << 1), (k << 2) - 1) invg[i] = 0;
tot++;
}
memset(tmp1, 0, sizeof(tmp1));
memset(tmp2, 0, sizeof(tmp2));
For (i, 0, n) tmp1[i] = rf[i];
For (i, 0, n - m) tmp2[i] = invg[i];
ff = 1; tot = 0;
while (ff <= (n << 1) - m) ff <<= 1, tot++;
orz = qpow(ff, ZZQ - 2);
For (i, 0, ff - 1)
rev[i] = (rev[i >> 1] >> 1) | ((i & 1) << tot - 1);
FFT(ff, tmp1, 1); FFT(ff, tmp2, 1);
For (i, 0, ff - 1) quot[i] = 1ll * tmp1[i] * tmp2[i] % ZZQ;
FFT(ff, quot, -1);
For (i, 0, ff - 1) quot[i] = 1ll * quot[i] * orz % ZZQ;
For (i, 0, n - m) if (i < n - m - i)
swap(quot[i], quot[n - m - i]);
For (i, n - m + 1, ff - 1) quot[i] = 0;
For (i, 0, n - m) printf("%d ", quot[i]); cout << endl;
ff = 1; tot = 0;
while (ff <= n) ff <<= 1, tot++;
orz = qpow(ff, ZZQ - 2);
For (i, 0, ff - 1)
rev[i] = (rev[i >> 1] >> 1) | ((i & 1) << tot - 1);
FFT(ff, g, 1); FFT(ff, quot, 1);
For (i, 0, ff - 1) xmod[i] = 1ll * g[i] * quot[i] % ZZQ;
FFT(ff, xmod, -1);
For (i, 0, ff - 1) xmod[i] = 1ll * xmod[i] * orz % ZZQ;
For (i, 0, n) xmod[i] = (f[i] - xmod[i] + ZZQ) % ZZQ;
For (i, 0, m - 1) printf("%d ", xmod[i]); cout << endl;
return 0;
}
二、常系数线性递推
#include <cmath>
#include <cstdio>
#include <cstring>
#include <iostream>
#include <algorithm>
#define For(i, a, b) for (i = a; i <= b; i++)
#define Step(i, a, b, x) for (i = a; i <= b; i += x)
#define Pow(k, n) for (k = 1; k < n; k <<= 1)
using namespace std;
inline int read()
{
int res = 0; bool bo = 0; char c;
while (((c = getchar()) < '0' || c > '9') && c != '-');
if (c == '-') bo = 1; else res = c - 48;
while ((c = getchar()) >= '0' && c <= '9')
res = (res << 3) + (res << 1) + (c - 48);
return bo ? ~res + 1 : res;
}
template <class T>
void Swap(T &a, T &b) {a ^= b; b ^= a; a ^= b;}
const int N = 5e5 + 5, ZZQ = 998244353;
int n, K, f[N], invf[N], orz[N], a[N], rev[N], tmp1[N], tmp2[N], df[N],
ff = 4, tot = 2, gg, res[N], bas[N], ans;
int qpow(int a, int b)
{
int res = 1;
while (b)
{
if (b & 1) res = 1ll * res * a % ZZQ;
a = 1ll * a * a % ZZQ;
b >>= 1;
}
return res;
}
void FFT(int n, int *a, int op)
{
int i, j, k;
For (i, 0, n - 1) if (i < rev[i]) Swap(a[i], a[rev[i]]);
orz[k = n >> 1, n] = qpow(3, op == 1 ? (ZZQ - 1) / n :
(ZZQ - 1) / n * (n - 1));
while (k) orz[k] = 1ll * orz[k << 1] * orz[k << 1] % ZZQ, k >>= 1;
Pow(k, n)
{
int x = orz[k << 1];
Step (i, 0, n - 1, k << 1)
{
int w = 1;
For (j, 0, k - 1)
{
int u = a[i + j], v = 1ll * w * a[i + j + k] % ZZQ;
a[i + j] = (u + v) % ZZQ;
a[i + j + k] = (u - v + ZZQ) % ZZQ;
w = 1ll * w * x % ZZQ;
}
}
}
}
void get_mod(int *f)
{
int i;
For (i, 0, K << 1) tmp1[i] = f[(K << 1) - i];
For (i, (K << 1) + 1, ff - 1) tmp1[i] = 0;
FFT(ff, tmp1, 1);
For (i, 0, ff - 1) tmp1[i] = 1ll * tmp1[i] * invf[i] % ZZQ;
FFT(ff, tmp1, -1);
For (i, 0, ff - 1) tmp1[i] = 1ll * tmp1[i] * gg % ZZQ;
For (i, K + 1, ff - 1) tmp1[i] = 0;
For (i, 0, K - 1 >> 1) Swap(tmp1[i], tmp1[K - i]);
FFT(ff, tmp1, 1);
For (i, 0, ff - 1) tmp1[i] = 1ll * tmp1[i] * df[i] % ZZQ;
FFT(ff, tmp1, -1);
For (i, 0, ff - 1) f[i] = (f[i] - 1ll * tmp1[i] * gg % ZZQ + ZZQ) % ZZQ;
For (i, K + 1, ff - 1) f[i] = 0;
}
int main()
{
int i, k;
n = read(); K = read();
For (i, 1, K) f[i] = df[K - i] = (ZZQ - read() % ZZQ) % ZZQ;
f[0] = df[K] = 1;
For (i, 0, K - 1) a[i] = (read() % ZZQ + ZZQ) % ZZQ;
invf[0] = 1;
Pow(k, K + 1)
{
gg = qpow(ff, ZZQ - 2);
For (i, 0, ff - 1)
rev[i] = (rev[i >> 1] >> 1) | ((i & 1) << tot - 1);
For (i, 0, (k << 1) - 1)
tmp1[i] = f[i], tmp1[i + (k << 1)] = 0;
For (i, 0, k - 1)
tmp2[i] = invf[i], tmp2[i + k] = tmp2[i + (k << 1)]
= tmp2[i + k * 3] = 0;
FFT(ff, tmp1, 1); FFT(ff, tmp2, 1);
For (i, 0, ff - 1) tmp1[i] =
(2 - 1ll * tmp1[i] * tmp2[i] % ZZQ + ZZQ) % ZZQ;
For (i, 0, k - 1)
tmp2[i] = invf[i], tmp2[i + k] = tmp2[i + (k << 1)]
= (tmp2[i + k * 3]) = 0;
FFT(ff, tmp2, 1);
For (i, 0, ff - 1) invf[i] = 1ll * tmp1[i] * tmp2[i] % ZZQ;
FFT(ff, invf, -1);
For (i, 0, ff - 1) invf[i] = 1ll * invf[i] * gg % ZZQ;
For (i, K + 1, ff - 1) invf[i] = 0;
ff <<= 1; tot++;
}
ff = 1; tot = 0;
while (ff <= K * 3) ff <<= 1, tot++;
For (i, 0, ff - 1)
rev[i] = (rev[i >> 1] >> 1) | ((i & 1) << tot - 1);
gg = qpow(ff, ZZQ - 2);
FFT(ff, invf, 1);
FFT(ff, df, 1);
bas[res[0] = 1] = 1;
while (n)
{
if (n & 1)
{
FFT(ff, res, 1);
For (i, 0, ff - 1) tmp1[i] = bas[i];
FFT(ff, tmp1, 1);
For (i, 0, ff - 1) res[i] = 1ll * res[i] * tmp1[i] % ZZQ;
FFT(ff, res, -1);
For (i, 0, ff - 1) res[i] = 1ll * res[i] * gg % ZZQ;
get_mod(res);
}
FFT(ff, bas, 1);
For (i, 0, ff - 1) bas[i] = 1ll * bas[i] * bas[i] % ZZQ;
FFT(ff, bas, -1);
For (i, 0, ff - 1) bas[i] = 1ll * bas[i] * gg % ZZQ;
get_mod(bas);
n >>= 1;
}
For (i, 0, K - 1) ans = (ans + 1ll * a[i] * res[i] % ZZQ) % ZZQ;
cout << ans << endl;
return 0;
}