[Luogu4238] 【模板】多项式求逆 [多项式求逆]

本文详细介绍了如何使用多项式求逆算法和NTT(Number Theoretic Transform)来解决特定数学问题,通过逐步推导,展示了如何从低精度解逐步提升到高精度解的倍增过程。

Link
Luogu - https://www.luogu.org/problemnew/show/P4238


Description
已知 F ( x ) F(x) F(x) 求次数最小的 G ( x ) ( m o d p = r 2 k + 1 ) G(x)\pmod{p=r2^k+1} G(x)(modp=r2k+1) 满足 F ( x ) ⊗ G ( x ) ≡ 1 ( m o d x n ) F(x)\otimes G(x)\equiv1\pmod{x^n} F(x)G(x)1(modxn)


倍增。
f i ( x ) ≡ F − 1 ( x ) ( m o d x 2 i ) f_i(x)\equiv F^{-1}(x)\pmod{x^{2^{i}}} fi(x)F1(x)(modx2i) ,显然有 f 0 ( x ) ≡ C ( m o d x ) f_0(x)\equiv C\pmod{x} f0(x)C(modx) 。其中 C C C F ( x ) F(x) F(x) 的常数项系数。
假设已知 f i ( x ) f_i(x) fi(x) ,要求 f i + 1 ( x ) f_{i+1}(x) fi+1(x)

显然 f i ( x ) ≡ f i + 1 ( x ) ( m o d x 2 i ) f_i(x)\equiv f_{i+1}(x)\pmod{x^{2^{i}}} fi(x)fi+1(x)(modx2i)
模数凑上去到 x 2 i + 1 x^{2^{i+1}} x2i+1 需要怎么操作呢……

正常选手的思路:这肯定要移项啊,然后还出 0 0 0 了,又很好随便搞一搞

请跳过下面的废话(

实际上模数翻倍就是有效长度翻倍
而且上面那个式子代表着 f i + 1 ( x ) f_{i+1}(x) fi+1(x) 截掉后面一半就是 f i ( x ) f_i(x) fi(x)

容易想到的一种构造方法是全部同乘 x 2 i x^{2^i} x2i 但是引进的 x 2 i x^{2^i} x2i 不好处理
这种构造方法的实际含义就是瞎jb把两个多项式一起向后平移
为什么要这么搞??有猫饼

现在我们有俩多项式
f i f_i fi    ■■■
f i + 1 ​ f_{i+1}\! fi+1 ■■■□□□
emmm 想想能够用这两个鬼东西得到什么不改变长度的等式关系
但是下边那个比上面那个多了一串 这个搞不了
那咋办 干脆搞一个右边是 0 0 0 的同余式?但是也不方便
而且要得到的东西里面怎么想也不会有把多项式按位对齐相乘吧。。
诶 但是左边两坨黑黑的东西一模一样 感觉可以消掉 相减一下
消完呢 干什么? 自己卷自己 什么都没了 左边的式子还在

f i + 1 ( x ) − f i ( x ) ≡ 0 ( m o d x 2 i ) f_{i+1}(x)-f_i(x)\equiv0\pmod{x^{2^i}} fi+1(x)fi(x)0(modx2i)
[ f i + 1 ( x ) − f i ( x ) ] 2 ≡ 0 ( m o d x 2 i + 1 ) [f_{i+1}(x)-f_i(x)]^2\equiv0\pmod{x^{2^{i+1}}} [fi+1(x)fi(x)]20(modx2i+1)
展开
f i + 1 2 ( x ) − 2 f i ( x ) f i + 1 ( x ) + f i 2 ( x ) ≡ 0 ( m o d x 2 i + 1 ) {f_{i+1}}^2(x)-2f_i(x)f_{i+1}(x)+{f_i}^2(x)\equiv0\pmod{x^{2^i+1}} fi+12(x)2fi(x)fi+1(x)+fi2(x)0(modx2i+1)
要把 f i + 1 ( x ) f_{i+1}(x) fi+1(x) 消剩一次挪到一边 ……然后呢 搞什么鬼 平方根??
其实不用,你想想这些 f f f F F F 的逆(类似情况一定要牢记!!逆跟原式相消)
两边同乘 F F F 即可。
f i + 1 ( x ) ≡ 2 f i ( x ) − f i 2 ( x ) F ( x ) ( m o d x 2 i + 1 ) f_{i+1}(x)\equiv2f_i(x)-f_i^2(x)F(x)\pmod{x^{2^i+1}} fi+1(x)2fi(x)fi2(x)F(x)(modx2i+1)
f i + 1 ( x ) ≡ f i ( x ) ∗ [ 2 − f i ( x ) F ( x ) ] ( m o d x 2 i + 1 ) f_{i+1}(x)\equiv f_i(x)*[2-f_i(x)F(x)]\pmod{x^{2^i+1}} fi+1(x)fi(x)[2fi(x)F(x)](modx2i+1)
快乐倍增。


有一个细节。NTT里面处理Rev数组的时候,为什么是在 n 2 \frac{n}{2} 2n 长度下翻转?
原因是:我们是把长度 2 i + 1 2^{i+1} 2i+1 f i ( x ) f_{i}(x) fi(x) F ( x ) F(x) F(x) 卷成一个长度为 2 i + 2 2^{i+2} 2i+2 的东西
然后再砍掉后面一半
但是你要怎么砍呢?干脆简单一点,不让多余的部分影响到的话,
直接不管就不会把多余的东西卷起来了……下次用到那一块位置的时候清掉就好……


观察多项式求逆的过程还可以发现:一个多项式有没有逆元完全取决于其常数项是否有逆元


#include<cstdio>
#include<iostream>
#include<algorithm>
#include<cstdlib>
#include<cstring>
#include<cmath>
#include<ctime>
#include<cctype>
using namespace std;
const int MOD = 998244353;
const int MAXN = 3e5+5;
const int ROOT = 3;
int n, a[MAXN], Wn[MAXN], G[MAXN], Rev[MAXN], Lim = 1;
inline int add(int a,int b) {a+=b;return a>=MOD?a-MOD:a;}
inline int sub(int a,int b) {a-=b;return a<0?a+MOD:a;}
inline int mul(int a,int b) {return 1LL*a*b%MOD;}
int qpowm(int a, int b, int p = MOD)
{
	static int ret;
	ret = 1;
	while (b > 0)
	{
		if (b & 1) ret = mul(ret,a);
		a = mul(a,a);
		b >>= 1;
	}
	return ret;
}
#define ksm(a,b) qpowm(a,b)
inline int Adjust(const long long& x, const long long& p)
{
	return (x>=p)?(x-p):x;
}
namespace Poly
{
	int Log, Tem;
	inline void Init(int n)
	{
		n >>= 1;
		Log = 0, Tem = 1;
		while (Tem <= n) Tem <<= 1, ++Log;
		Wn[0] = 1;
	}
	void NTT(int *a, const int& n, const int& Type)
	{
		Init(n);
		for (register int i = 1; i < n; ++i) Rev[i] = (Rev[i>>1]>>1)|((i&1)<<(Log-1));
		for (register int i = 1; i < n; ++i) if (Rev[i] > i) swap(a[Rev[i]], a[i]);
		for (register int Mid = 1, Len, Multi, Y, Cur; Mid < n; Mid <<= 1)
		{
			Len = Mid << 1;
			Multi = qpowm(ROOT, (MOD-1) / Len * Type + MOD - 1, MOD);
			for (register int i = 1; i < Mid; ++i) Wn[i] = mul(Wn[i-1], Multi);
			for (register int Pos = 0; Pos < n; Pos += Len)
			{
				for (register int Sub = 0; Sub < Mid; ++Sub)
				{
					Cur = Pos + Sub;
					Y = mul(a[Cur + Mid], Wn[Sub]);
					a[Cur+Mid] = sub(a[Cur], Y);
					a[Cur] = add(a[Cur], Y);
				}
			}
		}
		if (Type<0)
		{
			register int invn = qpowm(n, MOD-2, MOD);
			for (register int i = 0; i < n; ++i) a[i] =mul(a[i], invn);
		}
	}
	int tem[MAXN];
	void Inv(int *a, int *inv, const int& n)
	{
		inv[0] = qpowm(a[0], MOD-2, MOD);
		for (register int t, i = 2; i <= n; i <<= 1)
		{
			t = i << 1;
			fill(inv + (i >> 1), inv + t, 0);
			fill(tem + i, tem + t, 0);
			copy(a, a + i, tem);
			NTT(inv, t, 1);
			NTT(tem, t, 1);
			for (register int i = 0; i < t; ++i) inv[i] = mul(inv[i], sub(2, (1ll * tem[i] * inv[i]) % MOD));
			NTT(inv, t, -1);
		}
		fill(inv + n, inv + (n<<1), 0);
	}
}
inline void GetInput()
{
	scanf("%d", &n);
	for (register int i = 0; i < n; ++i) scanf("%d", &a[i]);
	while (Lim <= n) Lim <<= 1;
}
inline void Output()
{
	for (register int i = 0; i < n; ++i) printf("%d ", G[i]);
}
int main()
{
	GetInput();
	Poly::Inv(a, G, Lim);
	Output();
	return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值