原根和多项式学习笔记

1. 阶

1.1. 定义

假设模数 m 和底数 a 互质。

对于 \(n\in Z\)\(a^n \bmod m\) 呈循环结构,这种循环节的最小长度就是 a 模 m 的阶。

准确来说,对于 \(a\bot m\),满足同余式 \(a^n\equiv 1(\bmod m)\) 的最小正整数 n 称作 a 模 m 的阶,记作 \(\delta_m(a)\)

1.2. 幂的循环结构

利用阶,就可以刻画幂的循环结构,对于 \(a^n\bmod m\),记 \(n=k\delta_m(a)+r\),则 \(a^n\equiv a^r(\bmod m)\)

性质1:对于 \(a\in Z,m\in N+,a\bot m\)\(a^0,a^1,\dots a^{\delta_m(a)-1}\) 模 m 余数互不相同。

性质2:对于 \(a,n\in Z,m\in N+,a\bot m\)\(a^n\equiv 1(\bmod m)\) 成立当且仅当 \(n|\delta_m(a)\)

根据欧拉定理 \(a^{\varphi(m)}\equiv 1(\bmod m)\),所以对于所有 \(a\bot m\),必有 \(\delta_m(a)|\varphi(m)\),即 \(\varphi(m)\) 是所有 \(a\bot m\) 的阶的公倍数。

性质3:对于 \(a,k\in Z,m\in N+,a\bot m\),有 \(\delta_m(a^k)=\dfrac{\delta_m(a)}{gcd(\delta_m(a),k)}\)

1.3. 乘积的阶

设 a,b 是与 m 互质的不同整数,已知阶 \(\delta_m(a)\)\(\delta_m(b)\),那么可以得到:

性质4:

\(\dfrac{[\delta_m(a),\delta_m(b)]}{(\delta_m(a),\delta_m(b))}|\delta_m(ab)|[\delta_m(a),\delta_m(b)]\)

后半部分根据性质2易得,考虑前半段:

因为 \(1\equiv (ab)^{\delta_m(ab)\delta_m(b)}\equiv a^{\delta_m(ab)\delta_m(b)}(\bmod m)\),所以
\(\delta_m(a)|\delta_m(ab)\delta_m(b)\)

两侧消去 \((\delta_m(a),\delta_m(b))\),去掉互质部分,得到

\[\dfrac{\delta_m(a)}{(\delta_m(a),\delta_m(b))}|\delta_m(ab) \]

同理,

\[\dfrac{\delta_m(b)}{(\delta_m(a),\delta_m(b))}|\delta_m(ab) \]

因为左侧互质,所以就是之前的结论了。

同时 \(\delta_m(ab)=\delta_m(a)\delta_m(b)\Leftrightarrow \delta_m(a)\bot \delta_m(b)\)

性质5:对于 \(a,b\in Z,m\in N+,a,b\bot m\),存在 \(c\in Z\)\(c\bot m\) 使得:\(\delta_m(c)=[\delta_m(a),\delta_m(b)]\)

\(\delta_m(a)=\prod_p p^{a_p},\delta_m(b)=\prod_p p^{b_p}\)

根据 \(a_p\)\(b_p\) 的大小关系分成两类:

\[A=\{p:a_p\ge b_p\},B=A=\{p:a_p< b_p\} \]

\[xA=\prod_{p\in A} p^{a_p},xB=\prod_{p\in B} p^{b_p},yA=\prod_{p\in B} p^{a_p},yB=\prod_{p\in A} p^{b_p} \]

所以:\(\delta_m(a)=xAyA,\delta_m(b)=xByB\)

\[\delta_m(a^{yA})=xA,\delta_m(a^{yB})=xB \]

因为 \(xA\bot xB\)

所以:\(\delta_m(a^{yA}b_{yB})=xAxB=[\delta_m(a),\delta_m(b)]\)

2. 原根

2.1. 定义

对于 \(m\in N+\) ,如果存在 \(g\in Z,g\bot m\) 使得 \(\delta_m(g)=\varphi(m)\) 则 g 为 m 的原根。

但是并非所有整数 m 都存在原根,若存在,必然满足 \(g,g^2,\dots,g^{\varphi(m)}\) 所在同余类互不相同,特别地,对于素数 p,余数 \(g^i\bmod p\) 对于
\(i=1,2,\cdots,p-1\) 两两不同。

2.2. 原根判定定理

已知模数 \(\varphi(m)\) 的的所有质因子,易判断是否存在。

定理:对于整数 \(m>3\)\(g\bot m\) 那么 g 是 模 m 的原根当且仅当对于 \(\varphi(m)\) 的每个质因数 \(p\),都有 \(g^{\frac{\varphi(m)}{p}}\neq 1(\bmod m)\)

2.3. 原根个数

\(m\) 的原根个数为 \(\varphi(\varphi(m))\)

证明:

设 m 有原根 g。

\[\delta_m(g^k)=\dfrac{\delta_m(g)}{(\delta_m(g),k)}=\dfrac{\varphi(m)}{(\varphi(m),k)} \]

所以只有满足 \((\varphi(m),k)=1\) 时,才有原根。

2.4. 原根存在定理

模 m 的原根存在,当且仅当 \(m=1,2,4,p^c,2p^c\),p 是奇素数。

\(m=1,2,4\) 时,\(g=0,1,3\)

其他的在 wiki,不证了。

2.5. 原根求法

可以直接枚举每个数,然后 \(log^2\) check。

也可以随机,因为原根密度较高,期望复杂度 \(O((\log^2 m) \log \log m)\)

还有就是另一个瓶颈在于 \(\varphi(m)\) 的质因数分解。

2.6. 例题

P6091 【模板】原根

按照上述做法直接跑就行了。

#include<bits/stdc++.h>
#define ll long long
using namespace std;
const int M=1e6;
int T,n,d;
int p[M+5],phi[M+5],lst[M+5],cnt;
bool vis[M+5];
void init()
{
	phi[1]=1;
	for(int i=2;i<=M;i++)
	{
		if(!vis[i]) p[++cnt]=i,phi[i]=i-1,lst[i]=i;
		for(int j=1;i*p[j]<=M;j++)
		{
			vis[i*p[j]]=1,lst[i*p[j]]=p[j];
			if(i%p[j]) phi[i*p[j]]=phi[i]*(p[j]-1);
			else
			{
				phi[i*p[j]]=phi[i]*p[j];
				break;
			}
		}
	}
}
bool check(int x)
{
	if(x==2||x==4) return 1;
	int cnt=0,fl=0,v=0;
	while(x>1)
	{
		if(lst[x]==2) cnt++;
		else
		{
			if(v&&v!=lst[x]) fl=1;
			else v=lst[x];
		}
		x=x/lst[x];
	}
	if(cnt<=1&&!fl) return 1;
	return 0;
}
ll qpow(ll x,int y,int p)
{
	ll res=1;
	while(y)
	{
		if(y&1) res=res*x%p;
		x=x*x%p;
		y>>=1;
	}
	return res;
}
bool check(int x,int y)
{
	if(qpow(x,y,n)!=1) return 0;
	int v=y;
//	printf("%d %d\n",x,y);
	while(y>1)
	{
		if(qpow(x,v/lst[y],n)==1) return 0;
		y/=lst[y];
	}
	return 1;
}
int gcd(int a,int b)
{
	if(b==0) return a;
	return gcd(b,a%b);
}
int ans[M+5];
int main()
{
	srand((unsigned)time(0));
	init();
	scanf("%d",&T);
	while(T--)
	{
		scanf("%d%d",&n,&d);
		if(!check(n))
		{
			printf("0\n\n");
			continue;
		}
		printf("%d\n",phi[phi[n]]);
		int x=0,sum=0,val=1;
		while(1)
		{
			x=1ll*rand()*rand()%(n-1)+1;
			if(check(x,phi[n])) break;
		}
		ans[++sum]=x,val=x;
		for(int i=2;i<phi[n];i++)
		{
			val=1ll*val*x%n;
			if(gcd(i,phi[n])!=1) continue;
			ans[++sum]=val;
		}
//		printf("%d ",sum);
		sort(ans+1,ans+sum+1);
		for(int i=d;i<=sum;i+=d) printf("%d ",ans[i]);
		printf("\n");
	}
	return 0;
}
bzoj 1420. Discrete Root

首先 p 是质数,所以必然有原根且这些次幂覆盖了 1 到 p-1 的所有值。

找到一个 p 的原根 g,那么 \(g^k\) 必然是 p 的原根,所以必然存在 \((g^k)^x\equiv a(\bmod p)\)

交换一下, \((g^x)^k\equiv a(\bmod p)\)\(g^x\) 就是解,x 可以 bsgs

BZOJ2219 数论之神

首先模数一定是奇数,但是问题在于它不一定是质数,所以难以像上一个题一样直接做。

考虑拆分这个数,它一定会被分成若干个奇质数的幂次的相乘形式,对每个部分就可以处理了。

但是为什么能拆成这些部分?因为模数互质,考虑 CRT

首先,如果 \(p^k|b\) 那么要保证左边的 p 的次幂超过 k 就可以。

否则就是可以分成两部分,一个是 p 的幂次部分,这个可以直接算出来,然后同时除掉。

这样 b 和 mod 就互质了,然后可以按照上面的做法,先求原根,记为 g,设 \(b=g^t,x=g^s\) 然后就是同余方程的解数。

3. FFT

3.1. 多项式

数学定义中,多项式就是若干单项式之和,但是可以发现不同单项式的区别在于系数和次数。

所以这里有一个系数表示法,就是依次把系数写下来,就能得到一个向量。

所以就有暴力的乘法方法,每次依次将两个单项式对应相乘 \(O(n^2)\)

引理:一个 n 次多项式可以通过 n+1 个点来确定。

因为根据平常解方程的原理,一共 n+1 个未知数,所以需要 n+1 个不同的方程,当一维坐标确定时,就可以只用一个数组存储。

这东西叫点值表示法,这样乘的时候只需要把对应位置的值相乘就行了,但是转化的过程是 \(O(n^2)\) 的。

3.2. 复数

在实数域内,根号下必须是非负数,因为 \(a^2\ge 0\)

但是有一个东西 \(i\) 满足 \(i^2=-1\),就是虚数单位,没有现实意义。

形如 \(bi\) 形式的数称为纯虚数,形如 \(a+bi\) 的形式的数称为复数,\(a\) 是实部,\(b\) 是虚部。

然后就能得到复数域,复数可以作为方程的解,所以一个 n 次方程有且仅有 n 个根。

运算上,就是利用乘法分配律化成 \(a+bi\) 的形式。

除法拿出来说一下,因为这东西和根号一样,可以利用平方差公式把分母化成一个数,所以要同乘共轭复数化简。

\(\frac{z_1}{z_2}=\frac{a_1a_2+b_1b_2}{a_2^2+b_2^2}+\frac{a_2b_1-a_1b_2}{a_2^2+b_2^2}\)

同时复数也具有几何意义,但是它是由 a,b 两部分组成的,所以要用到坐标系,就是复平面。

欧拉公式\(e^{i\theta}=cos(\theta)+isin(\theta)\)

3.3. 单位根

考虑这样的一个方程,\(w^n=1\),根据上面的结论,一共有 n 个根,而这 n 个根叫做 n 次单位根,记作 \(w_n\)

将这些点画在复平面上,就形成了一个半径为 1 的圆,就是单位圆,并且可以发现是圆上的 n 等分点。

根据欧拉公式,可以直接求出单位根,\(w_n=e^{\frac{2i\pi}{n}}=cos(\frac{2\pi}{n})+isin(\frac{2\pi}{n})\)

其他的就是上面这个值的若干次方,有一个定理 \(w_n^k=w_{dn}^{dk}\),就是消去定理。

当 n 为偶数时,根据消去定理,\(w_n^{\frac{n}{2}}=w_2=-1\)

进一步得到 \(w_n^{\frac{n}{2}+m}=w_n^{\frac{n}{2}}*w_n^m=-w_n^m\)

折半定理:对于偶数 n,n 次单位根的平方构成的集合就是 \(\frac{n}{2}\) 次单位根的集合。

\((w_n^m)^2=(w_n^{\frac{n}{2})+m})^2\)

求和引理:对于正整数 n,k,满足 n 不为 k 因数,\(\sum_{i=0}^{n-1} (w_n^k)^i=0\)

3.4. 多项式转换的优化

首先一定要得到的就是第一个点的坐标,考虑能不能通过优化选点减少计算量。

首先可以发现如果是所有单项式都是偶数或奇数次幂,那么一定可以通过选相反数的方法减少运算。

如果是普通多项式,考虑拆解,这里我们默认 n 是偶数,否则补一个系数为 0 的项。

\(f1(x)=a_1x^0+a_3x^1+\dots,f2(x)=a_0x^0+a_2x^1+\dots\)

然后能得到 \(f(x)=xf1(x^2)+f2(x^2)\)

就出现问题了,没有实数的平方是负数,所以没办法继续递归。

3.5. 快速傅里叶变换

要求平方后还有负数出现,那么必须用到复数,而复数中单位根有比较好的性质。

我们就把上面的 x 带成单位根,得到:

\(f(w_n^m)=w_n^mf1((w_n^m)^2)+f2((w_n^m)^2)=w_n^mf1(w_{\frac{n}{2}}^m)+f2(w_{\frac{n}{2}}^m)\)

再带一下 \(w_n^{m+\frac{n}{2}}\),得到 \(f(w_n^{m+\frac{n}{2}})=w_n^mf1(w_{\frac{n}{2}}^m)-f2(w_{\frac{n}{2}}^m)\)

所以求出 \(f1(w_{\frac{n}{2}}^m)\)\(f2(w_{\frac{n}{2}}^m)\) 就能解出来。

然后就是一个分治了,这样就实现了式子->点值,时间复杂度 \(O(n\log n)\)

现在的问题是如何转回系数表示法,最简单的方法是拉插,\(O(n^2)\)

但是显然是不行的,把这个过程其实可以理解成矩阵乘法,此时我们需要乘一个逆矩阵得到原来的值。

因为矩阵里的东西都是复数,他们的倒数就是共轭复数,所以可以再做一遍 FFT。

3.6. 例题

【模板】多项式乘法(FFT)

题如其名,板子,按上面的直接做

#include<cstdio>
#include<cmath>
using namespace std;
const int N=3e6+5;
const double pi=3.1415926535897932384626433;
int n,m,len;
struct cplx{
	double re,imp;
	cplx operator +(const cplx &x)const{
		return (cplx){re+x.re,imp+x.imp};
	}
	cplx operator -(const cplx &x)const{
		return (cplx){re-x.re,imp-x.imp};
	}
	cplx operator *(const cplx &x)const{
		return (cplx){re*x.re-imp*x.imp,re*x.imp+imp*x.re};
	}
}a[N],b[N],c[N];
void FFT(cplx *a,int n,int opt)
{
	if(n==1) return;
	int m=(n>>1);
	cplx p1[m],p2[m];
	for(int i=0;i<m;i++) p1[i]=a[i*2],p2[i]=a[i*2+1];
	FFT(p1,m,opt),FFT(p2,m,opt);
	cplx w=(cplx){cos(2.0*pi/n),opt*sin(2.0*pi/n)},wk=(cplx){1,0};
	for(int i=0;i<m;i++,wk=wk*w)
	{
		a[i]=p1[i]+wk*p2[i];
		a[i+m]=p1[i]-wk*p2[i];
	}
}
int main()
{
	scanf("%d%d",&n,&m);
	for(int i=0;i<=n;i++)
	{
		scanf("%lf",&a[i].re);
	}
	for(int i=0;i<=m;i++)
	{
		scanf("%lf",&b[i].re);
	}
	len=1;
	while(len<=n+m) len<<=1;
//	printf("%d ",len);
	FFT(a,len,1),FFT(b,len,1);
	for(int i=0;i<len;i++)
	{
//		printf("%lf %lf %lf %lf\n",a[i].re,a[i].imp,b[i].re,b[i].imp);
		c[i]=a[i]*b[i];
	}
	FFT(c,len,-1);
	for(int i=0;i<=n+m;i++)
	{
		printf("%d ",(int)(c[i].re/len+0.5));
	}
	return 0;
}
Normal

考虑什么情况下 i 会对 j 产生贡献,必然在 j 被选中的时候 i,j 处在同一个连通块内。

方案数就是 \(\frac{dis(i,j)!}{(dis(i,j)+1)!}=\frac{1}{(dis(i,j)+1)!}\),所以就是要求每个距离的点对数。

考虑点分治,显然可以先求出根到每个点的 dis,然后进行合并,但是合并的过程是 \(n^2\) 的。

观察这东西的形式,可以发现 \(cnt_a\)\(cnt_b\) 会贡献到 a+b,很像多项式乘法的形式,所以 FFT 卷积即可。

Triple

如果是考虑顺序的完全背包,那么显然可以用 FFT 直接做。

难点在于不能重复选,考虑容斥,如果只选两个,那么不重复就是两种选法,重复的就是一个选两遍,对应减掉。

考虑 3 个的,全选成一样的可以直接判掉,两个一样的考虑打包再和单个的卷一遍,一共 3 种情况,乘上然后对应相减。

剩下的每个对应 6 种顺序,除掉之后和选 1-2 个的求和。

万径人踪灭

先枚举回文中心,然后合法的就是在两边相同的对里面任选,不连续这个要求二分 +hash 找回文串长度。

难点在于有多少对满足相等,可以发现因为同一个中心下标和固定。

所以可以把 ab 分开,然后标记出现位置为 1,其余为 0,这样只有乘积为 1 才相等,卷一下就可以了。

4. NTT

因为一道题拿 FFT 死活过不去,发现 NTT 可以处理模数,所以学习。

4.1. 原根

FFT 有一个很难受的问题,就是单位根的精度,以及这东西没办法取模

定义和求法在上面,考虑在做 FFT 的时候用到了哪些性质,记 p 的一个原根为 g。

首先 \(w_n^n=1\) 那么 \((g^r)^n=1\) 所以 \(r=\frac{\varphi(p)}{n}\)

我们让 \(g^r\) 作为新的单位根,考虑是否满足性质。

\(w_n^k=g^{\frac{(p-1)k}{n}}=g^{\frac{2(p-1)k}{2n}}=w_{2n}^{2k}\)

因为 \(g^{p-1}\neq g^{\frac{p-1}{2}},(g^{\frac{p-1}{2}})^2\equiv 1(\bmod p)\)

所以 \(g^{\frac{p-1}{2}}\equiv -1(\bmod p)\)

所以 \(w_{2n}^{k}\equiv -w_{2n}^{n+k}(\bmod p)\)

4.2. 例题

[SDOI2015] 序列统计

可以想到一个 \(O(m^2\log)\) 的做法,类似于快速幂,每次长度翻倍,枚举两边的乘积,暴力计算。

但是因为是相乘关系难以按正常的方法维护,考虑到若 \(a=c^s,b=c^t,ab=c^{s+t}\)

考虑取一个底数,然后弄出每个数的指数,因为要求取遍每个数,底数可以选原根。

这样就是一个加的形式,因为带取模,必须用 NTT。

#include<bits/stdc++.h>
#define ll long long
#define double long double 
using namespace std;
const int N=8005,mod=1004535809,g1=3,invg=334845270;
ll c[N*3],d[N*3];
int n,m,r,siz,a[N],pw[N],p[N];
ll sum[N],ans[N];
ll qpow(ll x,int y,int mod)
{
	ll res=1;
	while(y)
	{
		if(y&1) res=res*x%mod;
		x=x*x%mod;
		y>>=1;
	}
	return res;
}
bool check(int x)
{
	if(qpow(x,m-1,m)!=1) return 0;
	int y=m-1;
	for(int i=2;i*i<=y;i++)
	{
		if(y%i==0)
		{
			if(qpow(x,(m-1)/i,m)==1) return 0;
			while(y%i==0) y/=i;
		}
	}
	if(y!=1)
	{
		if(qpow(x,(m-1)/y,m)==1) return 0;
	}
	return 1;
}
void NTT(ll *a,int n,int opt)
{
	if(n==1) return;
	int m=n/2;
	ll p1[m],p2[m];
	for(int i=0;i<m;i++) p1[i]=a[i*2],p2[i]=a[i*2+1];
	NTT(p1,m,opt),NTT(p2,m,opt);
	ll wk=1,w=qpow(opt==1?g1:invg,(mod-1)/n,mod);
	for(int i=0;i<m;i++,wk=wk*w%mod)
	{
		a[i]=(p1[i]+wk*p2[i])%mod;
		a[i+m]=(p1[i]+mod-wk*p2[i])%mod;
	}
}
void solve(ll *a,ll *b)
{
	for(int i=1;i<m;i++)
	{
		c[i]=a[pw[i]],d[i]=b[pw[i]];
	}
	int len=1,l=0;
	while(len<=2*m) len*=2,l++;
	NTT(c,len,1),NTT(d,len,1);
	for(int i=0;i<len;i++) c[i]=c[i]*d[i]%mod;
	NTT(c,len,-1);
	for(int i=0;i<m;i++) b[i]=0;
	int inv=qpow(len,mod-2,mod);
	for(int i=1;i<len;i++)
	{
		int x=(i-1)%(m-1)+1;
		b[pw[x]]=(c[i]*inv+b[pw[x]])%mod;
//		printf("%d ",c[i]*inv%mod);
	}
//	printf("\n");
	for(int i=0;i<len;i++) c[i]=d[i]=0;
}
int main()
{
	srand((unsigned)time(0));
	scanf("%d%d%d%d",&n,&m,&r,&siz);
	for(int i=1;i<=siz;i++)
	{
		scanf("%d",&a[i]);
		if(a[i]==0) i--,siz--;
	}
	int g=0;
	while(1)
	{
		g=1ll*rand()*rand()%(m-1)+1;
		if(check(g)) break;
	}
//	printf("%d ",g);
	int v=1;
	for(int i=1;i<=m-1;i++)
	{
		v=v*g%m;
		pw[i]=v,p[v]=i;
//		printf("%d ",pw[i]);
	}
	ans[1]=1;
	for(int i=1;i<=siz;i++) sum[a[i]]=1;
	while(n)
	{
		if(n&1) solve(sum,ans);
		solve(sum,sum);
		n>>=1;
	}
	printf("%lld",(ans[r]+mod)%mod);
	return 0;
}
【模板】任意模数多项式乘法

这东西一是 \(\varphi(p)\) 不一定是 \(a2^k+1\),二是不一定有逆元。

考虑构造一组模数,求出对他们取模的结果,然后 CRT 确定唯一的值。

大概要取 3 个数,数的上界为 \(nV^2=10^{23}\),要求乘积大于上界,满足是 \(a2^k+1\),做3 遍 NTT。

注意常数!

点击查看代码
#include<cstdio>
#define ll long long
const int mod1=998244353,mod2=1004535809,mod3=469762049,g=3;
const int N=3e5+5;
using namespace std;
int n,m,p;
ll f1[N],f2[N],c1[N],c2[N],c3[N],v1[50],v2[50];
int d[N],a[N],b[N];
ll qpow(ll x,int y,int mod)
{
	ll res=1;
	while(y)
	{
		if(y&1) res=res*x%mod;
		x=x*x%mod;
		y>>=1;
	}
	return res;
}
ll mul(ll a,ll b,ll p)
{
	ll res=0;
	while(b)
	{
		if(b&1) res=(res+a)%p;
		a=(a+a)%p;
		b>>=1;
	}
	return res;
}
inline void NTT(int *a,int n,int opt,int mod,int t)
{
	if(n==1) return;
	int m=n/2;
	int p1[m],p2[m];
	for(int i=0;i<m;i++) p1[i]=a[i*2],p2[i]=a[i*2+1];
	NTT(p1,m,opt,mod,t+1),NTT(p2,m,opt,mod,t+1);
	ll w=(opt==1?v1[t]:v2[t]),wk=1;
	for(int i=0;i<m;i++,wk=wk*w%mod)
	{
		a[i]=(p1[i]+wk*p2[i])%mod;
		a[i+m]=(p1[i]-wk*p2[i])%mod;
	}
}
int len=1;
inline void solve(ll *c,int mod)
{
	for(int i=0;i<len;i++) a[i]=f1[i],b[i]=f2[i];
	int x=len;
	for(int i=1;i<=20&&x;i++)
	{
		v1[i]=qpow(g,(mod-1)/x,mod);
		v2[i]=qpow(qpow(g,mod-2,mod),(mod-1)/x,mod);
		x/=2;
	}
	NTT(a,len,1,mod,1),NTT(b,len,1,mod,1);
	for(int i=0;i<len;i++) d[i]=1ll*a[i]*b[i]%mod;
	NTT(d,len,-1,mod,1);
	ll inv=qpow(len,mod-2,mod);
	for(int i=0;i<len;i++)
	{
		c[i]=(d[i]*inv%mod+mod)%mod;
//		printf("%lld ",c[i]);
	}
//	printf("\n");
}
inline int read()
{
	int x=0;
	char ch=getchar();
	while(ch<48||ch>57) ch=getchar();
	while(ch>=48&&ch<=57) x=x*10+ch-48,ch=getchar();
	return x;
}
int main()
{
	n=read(),m=read(),p=read();
	for(int i=0;i<=n;i++)
	{
		f1[i]=read();
	}
	for(int i=0;i<=m;i++)
	{
		f2[i]=read();
	}
	while(len<=n+m) len=len*2;
	solve(c1,mod1),solve(c2,mod2),solve(c3,mod3);
	ll m1=1ll*mod1*mod2;
	for(int i=0;i<=n+m;i++)
	{
		ll t=mul(c1[i]*mod2%m1,qpow(mod2%mod1,mod1-2,mod1),m1)
		+mul(c2[i]*mod1%m1,qpow(mod1%mod2,mod2-2,mod2),m1);
		t%=m1;
		ll k=((c3[i]-t)%mod3+mod3)*qpow(m1%mod3,mod3-2,mod3)%mod3;
		ll ans=((k%p)*(m1%p)+t%p)%p;
		printf("%lld ",(ans+p)%p);
	}
	return 0;
}

5. 多项式其他运算

5.1. 多项式乘法逆

例题:P4238 【模板】多项式乘法逆

假设当前已经求出了 \(x^{\lceil\frac{n}{2}\rceil}\),考虑如何推到原来的 x。

设此时的多项式为 \(B'\),原来的为 \(B\),显然和 A 相乘后 \(\bmod x^{\lceil\frac{n}{2}\rceil}\) 都是 1。

然后做差得到 \((B'-B)\equiv 0(\bmod x^{\lceil\frac{n}{2}\rceil})\)

平方得到 \(B^2+B'^2-2BB' \equiv 0(\bmod x^{n})\)

两边同乘 A,得到 \(AB'^2+B-2B' \equiv 0(\bmod x^{n})\)

所以 \(B \equiv 2B'-AB'^2(\bmod x^{n})\)

因为多出来的高次项对答案没有影响,所以直接递推上去就行了。

5.2. 多项式开根

例题:P5205 【模板】多项式开根

按照上面的方法,考虑设 \(B'^2\equiv A(\bmod x^{\lceil\frac{n}{2}\rceil})\)

那么有 \(B^2-B'^2\equiv 0(\bmod x^{\lceil\frac{n}{2}\rceil})\)

平方得到:\(B^4+B'^4-2B^2B'^2\equiv 0(\bmod x^n)\)

\(B^4+B'^4+2B^2B'^2\equiv 4B^2B'^2(\bmod x^n)\)

两边开根得到:\(B^2+B'^2\equiv 2BB'(\bmod x^n)\)

因为 \(B^2\equiv A(\bmod x^n)\),代换再整理得到:

\(B\equiv \dfrac{A+B'^2}{2B'}\)

posted @ 2025-12-06 16:42  wangsiqi2010916  阅读(38)  评论(0)    收藏  举报