loading...

多项式总结

Lagrange 插值法

板题

给定 \(n\) 个点 \((x_i,y_i)\),确定一个 \(n-1\) 次多项式 \(f(x)\).一般只要求给出 \(f(k)\) 的值即可.

朴素解法

使用高斯消元法,待定系数法代入每个 \(x_i\) 即可,时间复杂度 \(O(n^3)\).

Lagrange 插值(\(x_i\) 随机)

\(f(x)=\sum a_m x^m\).

尝试构造 \(p_i\),使得:

\[p_i(x)=\begin{cases} 1 & x=x_i\\ 0 & x=x_j(j\not = i) \end{cases} \]

什么东西可以满足这个呢?于是又找到了:

\[l_i(x) = \prod _{i\not = j}^{}(x-x_j) \]

所有 \(x_1,x_2,\dots,x_n\) 中,只有 \(l_i(x_i) \not = 0\),所以把它变成 \(p_i(x)\) 也很简单:

\[p_i(x) = \dfrac{l_i(x)}{l_i(x_i)} \]

\(p_i\) 来表示 \(f(x)\)

\[f(x)=\sum _{i=1}^{n} p_i(x) \times y_i \]

这是显然的,因为 \(p_i(x)\) 只在 \(x=x_i\) 时值为 \(1\),其它值 \(x_j\) 代入都为 \(0\).

于是可以展开:

\[f(x) = \sum _{i=1}^{n} \left(y_i\prod _{i\not = j}\dfrac{x-x_j}{x_i-x_j}\right) \]

暴力 \(n^2\) 展开或求值就可以了.

\(p_i\) 的过程暴力 \(O(n^2)\),求值也是 \(O(n^2)\).

\(x_i\) 自己定

可以自己找一些性质比较好的 \(x_i\).

例题:给定 \(n,k\),求 \(\sum\limits_{i=1}^{n} i^k\).要求在与 \(n\) 无关的 \(O(k \log k)\) 的时间内解决.

猜想答案是一个关于 \(n\) 的多项式,令为 \(f_k(n)\).

  • 对于 \(k=0\)\(f_0(n) = n\)
  • 对于 \(k=1\)\(f_1(n) = \dfrac{n(n+1)}{2}=\dfrac{n^2}{2}+\dfrac{n}{2}\).
  • \(\dots\)

根据前辈的观察发现他就是个 \(k+1\) 次多项式,证明略.

\(k+2\)\(n\) 暴力求,复杂度为 \(O(k\log k)\).插值即可.但是暴力插是 \(O(k^2)\) 的.

选哪些 \(n\) 可以算得更快?

\(O(k^2)\) 插值的瓶颈在于 \(x_i-x_j\) 那一坨的求值,其他...

  • \(x-x_j\)\(x\) 值确定情况下可以 \(O(n)\) 预处理所有 \(\prod (x-x_j)\),调用时乘上 \(x-x_i\) 的逆元即可.
  • 枚举 \(i\) 求值是 \(O(1)\) 的,总的来说是 \(O(n)\).

\(x_i=i\)\(\prod _{i\not ={j}} (x_i-x_j)\) 就好求了,分成 \(i<j\)\(i>j\) 两段,于是就等于 \((-1)^{i-1}(i-1)!(n-i)!\),可以预处理.

于是 \(O(k\log k)\) 求值,\(O(n)\) 插值就完了.

与 DP 结合

例题:calc

暴力 DP:\(f(n,m)\)\(a_n \le m\) 的长为 \(n\) 的单调不减数列个数.因为实际上求得是无序,答案为 \(f(n,k) \times n!\).\(f(0,i)=1\).

\[f(n,m)=f(n-1,m-1) \times m + f(n,m-1) \]

猜想 \(f(n,m)\)\(n\) 固定时是关于 \(m\) 的多项式 \(g_n(m)\),证明如下.

按照套路,寻找转移式中的差分:

\(g_n(m)-g_n(m-1)=g_{n-1}(m-1) \times m\)

假设 \(g_{n-1}(x)\) 是多项式成立.显然 \(g_0(x)\)\(0\) 次多项式.

那么右边的式子就是一个多项式.由于是差分,累加得到 \(g_n(m)-g_n(0)=m\sum \limits_{i=1}^{m}g_{n-1}(i-1)\)

\(g_n(0)=0\),所以因为左边只剩 \(g_n(m)\),而右边 \(g_{n-1}(i-1)\) 也是多项式,所以左边也是多项式.\(m\sum \limits_{i=1}^{m}\) 使得 \(g_{n}(m)\) 的次数为 \(g_{n-1}(i-1)\) 次数 \(+2\).

数学归纳法得到:\(g_k(x)\)\(2k\) 次多项式.

直接暴力求解 \(g_n(m)\)\(2n+1\) 项,\(O(n)\) 插值即可.

多项式乘法(卷积),快速幂

Tips:傅里叶变换与 OI 关系不大,所以不说.

离散傅里叶变换 DFT

一个 \(n\) 次多项式 \(f(x) = \sum \limits_{m=0}^n a_mx^m\), 我们一般采用系数表示法,也就是只保存 \(a_m\) 的值.

这种表示方法的局限性在于做多项式乘法(卷积)时,两个相乘的多项式都要枚举 \(a_m\),总的时间复杂度是 \(O(n^2)\) 的.

众所周知,多项式 \(f(x)\) 可以用 \(n+1\) 个点值表示出来.

多项式的乘法可以在特定的 \(x\) 处轻松表示出来:看多项式乘法的定义式:\(h(x)=f(x)g(x)\),就是用 \(f(x)\)\(g(x)\) 的点值表达式 \((x,f(x)),(x,g(x))\)\(y\) 值大小相乘得到 \((x,f(x)g(x))\),结果就是 \(h(x)\) 的点值表达式.

我们不妨找出 \(n+1\) 个特定的 \(x_i\) 值得到每个 \(f(x_i),g(x_i)\),然后相乘得到 \(h(x_i)\),在将其还原.

但求值的过程是 \(O(n)\) 的,求 \(O(n)\) 个点的对应点值的复杂度飙到了 \(O(n^2)\),还不如直接暴力卷呢.如何更快?

快速傅里叶变换 FFT

前置知识简述

复数的代数表示法:一个复数可以形容成 \(x+y\mathrm{i}\)\(\mathrm{i}\) 是虚数单位表示 \(\sqrt{-1}\).

三角表示法:复数 \(z=r(\cos \theta +\mathrm{i}\sin\theta)\).\(r\) 就是代数表示法中 \((0,0)\) 连到 \((a,b)\) 线段长度,\(\theta\) 就是其与 \(x\) 轴的夹角.复数相乘相当于 \(r\) 相乘,\(\theta\) 相加.

寻找一些有好的性质的点,尽量能够重复利用之前的计算结果,DFT 的效率将能得到质变.

在复数集 \(\mathbb{C}\) 中,有一系列周期性变化的数 \(\omega_n^k\),名叫单位复根.在复平面上,这 \(n\) 个数呈现将单位圆平均分为 \(n\) 分的样子,就像这样(一个 \(n=8\) 时的例子):

图片用 desmos 生成

单位复根的定义:\(\omega_n^k=e^{\mathrm{i}\frac{2k\pi}{n}}=\cos \dfrac{2k\pi}{n}+\mathrm{i} \sin \dfrac{2k\pi}{n}\)

性质:

  1. \(\omega _{2n}^{2k}=\omega_n^k\)
  2. \(\omega_n^{k+\frac{n}{2}}=-\omega_{n}^k\)
  3. \(\omega_n^n=\omega_n^0=1\)

这几个性质将会在 FFT 中发挥重要作用.


考虑将单位复根 \(\omega_n^0,\omega_n^1,\dots,\omega_n^{n-1}\) 代入 \(n\) 的多项式(注意次数变成了项数).

不妨设 \(n=2^k,k\in \mathbb{N}\).这样 \(n\) 可以一直分成两个子问题递归.如果 \(n\) 不是 \(2\) 的整数次幂,补位即可.

\(f(x)=\sum\limits_{m=0}^{n-1} a_mx^m\).

考虑将它拆分成奇次幂和偶次幂:

\[f(x)=\sum_{m=0}^{\frac{n}{2}-1}\left\{a_{2m}(x^2)^{m}\right\}+x\sum_{m=0}^{\frac{n}{2}-1}\left\{a_{2m+1}(x^2)^{m}\right\} \]

定义下面两个多项式:

\[\begin{aligned} A(x)=a_0x^0+a_2x^1+\dots+a_{2n-2}x^\frac{n}{2}\\ B(x)=a_1x^0+a_3x^1+\dots+a_{2n-1}x^\frac{n}{2} \end{aligned} \]

于是:

\[f(x)=A(x^2)+xB(x^2) \]

分别代入 \(\omega_n^k,\omega_n^{k+\frac{n}{2}}\).注意到 \(A,B\) 均是偶函数.

\[\begin{aligned} f(\omega_n^k)&=A(\omega_{n}^{2k})+\omega_n^kB(\omega_n^{2k})\\ &=A(\omega_{\frac{n}{2}}^k)+\omega_n^kB(\omega_{\frac{n}{2}}^k)\\ \\ f(\omega_n^{k+\frac{n}{2}})&=A(\omega_{n}^{2k})+\omega_n^{k+\frac{n}{2}}B(\omega_n^{2k})\\ &=A(\omega_{\frac{n}{2}}^k)-\omega_n^kB(\omega_{\frac{n}{2}}^k) \end{aligned} \]

我们现在把问题分解成了 \(A(x)\)\(B(x)\) 这两个 \(\dfrac{n}{2}\) 项多项式分别代入 \(\omega_{\frac{n}{2}}^0,\omega_{\frac{n}{2}}^1,\dots,\omega_{\frac{n}{2}}^{\frac{n}{2}-1}\) 求值的子问题.问题一分为二,所以总共会分出 \(\log_2 n\) 层,每一层总共是 \(O(n)\) 次求值,时间复杂度为 \(O(n \log n)\).边界在 \(n=1\) 时,直接返回常数项.

可以写分治解决,但有一种常数更优秀的做法,待会介绍.

快速傅里叶逆变换 IFFT

我们还需要将点值转成系数,一个更常用的表示形式.

考虑如何反解 \(a_i\).现在我们存储着每一个 \(f(\omega_n^k)\) 的值:

\[\begin{aligned} y_k:=&f(\omega_n^k)=\sum_{m=0}^{n-1}a_m\omega_n^{mk}\\ F(x):=&\sum_{m=0}^{n-1} y_mx^m \end{aligned} \]

愉快地往 \(F(x)\) 中代入 \(\omega_{n}^{0},\omega_{n}^{-1},\dots,\omega_{n}^{-n+1}\),得到:

\[\begin{aligned} F(\omega_{n}^{-k})&=\sum_{m=0}^{n-1}\sum _{l=0}^{n-1}a_l\omega_{n}^{(l-k)m}\\ &=\sum _{l=0}^{n-1}a_l\sum_{m=0}^{n-1}(\omega_{n}^{l-k})^m\ \end{aligned} \]

注意到 \(\sum\limits_{m=0}^{n-1}(\omega_{n}^{l-k})^m\) 的取值在 \(l=k \pmod{n}\) 时取 \(n\)\(l\not ={k}\pmod{n}\) 时取 \(0\),因此,\(F(\omega_n^{-k})=a_k\times n\).

根据 \(\omega_n^{k}\) 的周期性,\(\omega_n^{-k}=\omega_{n}^{n-k}\).综上,我们仍然可以用与 FFT 相似的方法分治求 \(a_i\).

具体地,把 FFT 中 \(\omega_n^k\) 逐个改为 \(\omega_n^{-k}\),最后 FFT 出来的结果全部除以 \(n\) 就得到了 \(a_i\).

实现 luogu P3803

分治的方法很暴力,不多说了.我也只写过倍增.

假设 \(n=2^k\).

模拟分治的过程,可以得出一种倍增写法,辅助空间仅为 \(O(1)\):分治共有 \(k\) 层,第 \(k\)\(a_i\) 的顺序是什么样的呢?

结论:对 \(i\) 二进制拆解,然后反转,就是 \(a_i\) 的位置 \(i'\).例如 \(n=2^3\) 时,在第 \(k\)\(a_{(001)_2}\) 实际处于 \((100)_2\) 位置,也就是 \(a_1\) 处于 \(8\) 位置.证明略.

暴力比较好想,但没有优化好写.我们可以直接写出 \(O(n)\) 的代码.

#define rep(i, l, r) for(int i = l; i <= r; ++i)
inline void change() {
	int n = f.size();
	vector<int> rev(n);
	rep(i, 1, n-1) rev[i] = rev[i>>1]>>1|((i&1)*(n>>1));
	rep(i, 0, n-1) if (i < rev[i]) swap(f[i], f[rev[i]]);
}

这个函数将 \(f\) 的每一位转到了它在第 \(k\) 层的实际位置.\(\tt rev[i]\) 表示 \(i\) 的实际位置.按 \(0 \to n-1\) 的顺序依次寻找,显然 \(\tt rev[0]=0\).\(\left\lfloor\dfrac{i}{2}\right\rfloor\) 的二进制反转应该是 \(i\) 的二进制反转前面若干位,因此直接赋值.当 \(i\) 二进制最后一位是 \(1\) 时,还要赋给第 \(k-1\) 位为 \(1\).

然后模拟分治每一层合并操作即可.这里可以把 IFFT 也放一起,on=-1 时执行 IFFT.

inline void fft(int on) {
	int n = f.size();
	change();
	for (int h = 2; h <= n; h<<=1) {
		comp wn = comp(cos(2*PI/h), on*sin(2*PI/h));    // 单位根一次的变化量
		for (int j = 0; j < n; j += h) {
			comp w = comp(1, 0);    // 单位根
			for (int i = j; i < j+(h>>1); ++i, w = w*wn) {
				int k = i+(h>>1);
				comp a = f[i], b = w*f[k];
				f[i] = a+b, f[k] = a-b;
			}
		}
	}
	if (on == -1) {
		rep(i, 0, n-1)
            f[i] /= n;
	}
}

模板题完整代码

#include <bits/stdc++.h>
#define rep(i, l, r) for (int i = l; i <= r; ++i)
#define per(i, r, l) for (int i = r; i >= l; --i)
#define LL long long
#define int long long
#define comp complex<double>
using namespace std;
const int N = 1<<21, mod = 998244353;
const double PI = acos(-1);
inline int qpow(int a, int b) {
	int res = 1;
	for (; b; b >>= 1, a=a*a%mod) if (b&1) res = res*a%mod; 
	return res;
}
int n, m, k;
struct fft_poly {
	vector<comp> f;
	fft_poly(int n, comp v) : f(vector<comp>(n, v)) {}
	inline auto begin() { return f.begin(); }
	inline auto end() { return f.end(); }
	inline auto size() { return f.size(); }
	inline auto resize(int n, int v) { return f.resize(n, v); };
	inline comp& operator[](int x) { return f[x]; }
	inline void change() {
		int n = f.size();
		vector<int> rev(n);
		rep(i, 1, n-1) rev[i] = rev[i>>1]>>1|((i&1)*(n>>1));
		rep(i, 0, n-1) if (i < rev[i]) swap(f[i], f[rev[i]]);
	}
	inline void fft(int on) {
		int n = f.size();
		change();
		for (int h = 2; h <= n; h<<=1) {
			comp wn = comp(cos(2*PI/h), on*sin(2*PI/h));
			for (int j = 0; j < n; j += h) {
				comp w = comp(1, 0);
				for (int i = j; i < j+(h>>1); ++i, w = w*wn) {
					int k = i+(h>>1);
					comp a = f[i], b = w*f[k];
					f[i] = a+b, f[k] = a-b;
				}
			}
		}
		if (on == -1) {
			rep(i, 0, n-1)
                f[i] /= n;
		}
	}
	inline friend fft_poly operator * (fft_poly a, fft_poly b) {
		int n = a.size()+b.size();
		int k = ceil(log2(n));
		n = 1<<k;
		fft_poly c(n, 0);
		a.resize(n, 0), b.resize(n, 0);
		a.fft(1), b.fft(1);
		rep(i, 0, n-1) c[i] = a[i]*b[i];
		c.fft(-1);
		return c;
	}
	inline friend void qpow(fft_poly& res, fft_poly& a, int b) {
		for (; b; b >>= 1) {
			if (b & 1) res = res*a;
			a = a*a;
		}
	}
};
signed main() {
    ios::sync_with_stdio(false), cin.tie(0), cout.tie(0);
    cin >> n >> m;
    fft_poly f(n+1, 0), g(m+1, 0);
    for (auto& it : f) cin >> it;
    for (auto& it : g) cin >> it;
    f = f*g;
    rep(i, 0, n+m) cout << (int)(f[i].real()+0.5) << ' ';
    return 0;
}   

快速数论变换 NTT

快速傅里叶变换本身没问题,但计算机上它的精度......

能不能在整数范围上就可以实现更快的 DFT?在数论板块上有这样一个与单位复根类似的数.

原根简述

定义:若 \(\nexists x\in \mathbb{N},x < \varphi(p):g^x\not\equiv 1\pmod p\),则称 \(g\)\(p\) 的原根.\(\varphi(p)\) 为欧拉函数,表示与 \(p\) 互质且小于等于它的正整数个数.

性质(\(p\) 为素数):

  1. \(g^1,g^2,\dots,g^{\varphi(p)}\) 在模 \(p\) 意义下互不相等.
  2. \(g^n \equiv g^{n+\varphi(p)}\pmod p\).(循环性)

对于一个素数 \(p\) 来说 \(\varphi(p)=p-1\),若能满足 \(\left.n=2^k\right|p-1\),原根 \(g\) 就可以替代 \(\omega_n\) 了.这个素数模数我们一般取 \(p=998244353=2^{23}\times7\times17+1\),这时 \(g=3\).更多模数参见NTT 模数和原根表.

具体地,\(\omega_n^k\) 可以用 \(g^{\frac{(p-1)k}{n}}\) 替换即可达到相同的效果.

注意除法改为乘逆元,勤取模.

模板题完整代码

#include <bits/stdc++.h>
#define rep(i, l, r) for (int i = l; i <= r; ++i)
#define per(i, r, l) for (int i = r; i >= l; --i)
#define LL long long
#define int long long
#define comp complex<double>
using namespace std;
const int N = 1<<21, mod = 998244353;
inline int qpow(int a, int b) {
	int res = 1;
	for (; b; b >>= 1, a=a*a%mod) if (b&1) res = res*a%mod; 
	return res;
}
int n, m, k;
struct ntt_poly {
	vector<int> f;
	ntt_poly(int n, int v) : f(vector<int>(n, v)) {}
	inline auto begin() { return f.begin(); }
	inline auto end() { return f.end(); }
	inline auto size() { return f.size(); } 
	inline auto resize(int n, int v) { return f.resize(n, v); };
	inline int& operator[](int x) { return f[x]; }
	inline void change() {
		int n = f.size();
		vector<int> rev(n);
		rep(i, 1, n-1) rev[i] = rev[i>>1]>>1|((i&1)*(n>>1));
		rep(i, 0, n-1) if (i < rev[i]) swap(f[i], f[rev[i]]);
	}
	inline void ntt(int on) {
		int n = f.size();
		change();
		for (int h = 2; h <= n; h<<=1) {
			int wn = qpow(on==1?3:qpow(3,mod-2), (mod-1)/h);
			for (int j = 0; j < n; j += h) {
				int w = 1;
				for (int i = j; i < j+(h>>1); ++i, w = w*wn%mod) {
					int k = i+(h>>1);
					int a = f[i], b = w*f[k]%mod;
					f[i] = (a+b)%mod, f[k] = (a-b+mod)%mod;
				}
			}
		}
		if (on == -1) {
			int inv = qpow(n, mod-2);
			rep(i, 0, n-1)
				f[i] = f[i]*inv%mod;
		}
	}
	inline friend ntt_poly operator * (ntt_poly a, ntt_poly b) {
		int n = a.size()+b.size();
		int k = ceil(log2(n))+0.5;
		n = 1<<k;
		ntt_poly c(n, 0);
		a.resize(n, 0), b.resize(n, 0);
		a.ntt(1), b.ntt(1);
		rep(i, 0, n-1) c[i] = a[i]*b[i]%mod;
		c.ntt(-1);
		return c;
	}
	inline friend void qpow(ntt_poly& res, ntt_poly& a, int b) {
		for (; b; b >>= 1) {
			if (b & 1) res = res*a;
			a = a*a;
		}
	}
};
signed main() {
    ios::sync_with_stdio(false), cin.tie(0), cout.tie(0);
    cin >> n >> m;
    ntt_poly f(n+1, 0), g(m+1, 0);
    for (auto& it : f) cin >> it;
    for (auto& it : g) cin >> it;
    f = f*g;
    rep(i, 0, n+m) cout << f[i] << ' ';
    return 0;
}   

经典题目

卷积形式转化-快速傅立叶之二

例题:现有 \(\{a_i\}\)\(\{b_i\}\) 两个长为 \(n\) 的序列,令 \(c_k=\sum\limits_{i=k}^na_ib_{i-k}\).下标从 \(0\) 开始.

\(\{a_i\}\) 反转得到 \(\{a'_i\}\),则 \(a'_{n-i-1}=a_i\).

\[c_k=\sum_{i=k}^na'_{n-i-1}b_{i-k}=\sum_{i+j=n-k-1}a'_ib_j \]

这是一个卷积的形式,直接上 FFT 即可.

高精度乘法(加强版)

例题:给定正整数 \(a,b\),求 \(a \times b\).

\(x=10\)\(a,b\) 可以分别写做多项式,直接上 FFT 或 NTT 都可.

串匹配

给定主串 \(S\) 和模式串 \(T\)\(10^5\ge|S|\ge|T|\ge1\).下标从 \(0\) 开始.\(n = |S|,m=|T|\).

例题 \(1\):对于 \(0\le i \le n-m\),给出 \(T\) 是否与 \(S\)\(i\) 开始的 \(m\) 个字符组成的子串相等.

例题 \(2\):对于 \(0\le i \le n-m\),给出 \(T\)\(S\)\(i\) 开始的 \(m\) 个字符组成的子串能匹配上多少个字符,也就是 \(\sum \limits_{j=0}^{m-1}[S_{i+j}=T_j]\).

例题 \(3\)\(S,T\) 中存在通配符,可以与任何字符匹配.同样求例题 \(1\) 的答案.

  • 例题 \(1\)

    • 法一:\(n\) 项式 \(f\)\(i\) 次项系数 \(f_i=S_i\)\(m\) 项式 \(g\)\(i\) 次项系数 \(g_i=T_{m-i-1}\).这里指 ASCII 码值.

      构造 \(c_k=\sum\limits_{i=0}^{m-1}(S_{i+k}-T_i)^2=\sum\limits_{i=0}^{m-1}(f_{i+k}-g_{m-i-1})^2\),当且仅当 \(c_k=0\) 时,\(k\) 处答案为‘是’.

      \(f\)\(g\) 分别 DFT 后,\(ret_i:=(f_i-g_i)^2\),再把 \(ret\) 逆变换回去,则 \(c_k=ret_{m+k-1}\).时间复杂度 \(O(n \log n)\).

    • 法二:分别考虑每一个字符,假设当前考虑 \(ch\).令 \(f_i=[ch \neq S_i],g_i=[ch = T_i]\).

      注意这个逻辑:由于当前只考虑 \(ch\),因此 \(g_i=1\) 表示 \(T_i\) 必须有对应的 \(S_{i+k}\) 来匹配它,而 \(f_i=0\) 表示 \(S_i\) 这一位可以匹配上 \(T_{i-k}\).当且仅当存在一位需要匹配的 \(T_i\) 没有匹配到 \(S_{i+k}\),两者相乘为 \(1\).

      因此,令 \(h_k=\sum\limits_{i=0}^{m-1}f_{i+k}g_i\),则 \(h_k=0\) 表示仅考虑 \(ch\) 的情况下能匹配得起.只有每个字符得到的 \(h_k\) 均等于 \(0\)\(k\) 处答案为‘是’.时间复杂度 \(O(|\Sigma|n\log n)\)

  • 例题 \(2\)

    需要另一个奇妙的构造,借鉴例题 \(1\) 的法二,我们略改一改.改 \(f_i=[ch=S_i],\)\(h_k=\sum\limits_{i=0}^{m-1}f_{i+k}g_i\),只有当 \(S_{i+k}=T_i\) 时,相乘等于 \(1\) 会被统计进答案.对每一个字符做相同的卷积,每次得到的 \(h_k\) 加和得到答案.时间复杂度 \(O(|\Sigma|n\log n)\).

  • 例题 \(3\)

    • 法一:模仿例题 \(1\) 法一,令通配符的对应 \(f_i=0,g_{m-j-1}=0\).构造 \(c_k=\sum\limits_{i=0}^{m-1}(f_{i+k}-g_{m-i-1})^2f_{i+k}g_{m-i-1}\),当且仅当 \(c_k=0\) 时,\(k\) 处答案为‘是’.

      正确性不证自明.

    • 法二:模仿例题 \(1\) 法二,考虑每个字符 \(ch\) 时,把通配符当作 \(ch\) 来看.

多项式快速幂

您这么神仙,肯定会自己发明,不要欺负蒟蒻了 T_T.

posted @ 2025-02-10 16:23  goldspade  阅读(92)  评论(2)    收藏  举报