【学习笔记】之多项式使人头秃

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/hanyuweining/article/details/84936710

真的自闭= =

多项式是什么鬼哦

首先 介绍 FFT

我才不想写那么多柿子呢

大体说一下FFT干了啥

我们对两个多项式进行卷积(即多项式乘法)

f=a*b 也就是 f_i =\sum_{j=0}^i a_j * b_{i-j}

暴力计算的话是n^2的

我们考虑把它变成点值[即(x,y)表示f(x)=y] 点值相乘就快了嘛 但是变成点值了以后咋变回来呢

有个叫傅里叶的nb的人 他发明了一个nb的东西叫傅里叶变换= =

也就是通过 虚数中的单位根 来计算就可以变回来了

单位根是什么东西呢 就是在复平面上的一个单位圆 将其弧等分成若干份 第一个点位于(0,1)的n个点 把这些数带进去就可以做啦

说起来很奇特对不对 他其实就很奇特= =

具体详细的东西右转百度吧 我实在是懒得写QAQ

 实现上可以直接使用模板库里的complex(虽然我用起来非常不习惯

扔个代码跑路。

#include<cstdio>
#include<algorithm>
#include<cstring>
#include<cmath>
#define maxn 3000010
using namespace std;

const double Pi = acos(-1.0);

struct complex
{
    double x,y;
    complex(double xx=0.0,double yy=0.0){x=xx,y=yy;}
}A[maxn],B[maxn];
int l,r[maxn],limit=1;
complex operator + (complex a,complex b){return complex(a.x+b.x,a.y+b.y);}
complex operator - (complex a,complex b){return complex(a.x-b.x,a.y-b.y);}
complex operator * (complex a,complex b){return complex(a.x*b.x-a.y*b.y,a.x*b.y+a.y*b.x);}

void FFT(complex *a,int type)
{
    for(int i=0;i<limit;i++)
        if(i<r[i])	swap(a[i],a[r[i]]);
    for(int mid=1;mid<limit;mid<<=1)
    {
        complex Wn=complex(cos(Pi/mid),type*sin(Pi/mid));
        for(int R=mid<<1,j=0;j<limit;j+=R)
        {
            complex w=complex(1,0);
            for(int k=0;k<mid;k++,w=w*Wn)
            {
                complex x=a[j+k],y=w*a[j+mid+k];
                a[j+k]=x+y;
                a[j+mid+k]=x-y;
            }
        }
    }
}

int main()
{
    int N,M,i,j;
    scanf("%d%d",&N,&M);
    for(i=0;i<=N;i++)	scanf("%lf",&A[i].x);
    for(i=0;i<=M;i++)	scanf("%lf",&B[i].x);
    while(limit<=(N+M))	limit<<=1,l++;
    for(i=0;i<limit;i++)
        r[i]=(r[i>>1]>>1)|((i&1)<<(l-1));
    FFT(A,1);FFT(B,1);
    for(i=0;i<=limit;i++)
        A[i]=A[i]*B[i];
    FFT(A,-1);
    for(i=0;i<=N+M;i++)
        printf("%d ",(int)(A[i].x/limit+0.5));
    return 0;
}

然后我们就遇到了一个神奇的模数 998244353 才不是1XXXXXX7

为什么是这个模数呢 因为他是一个2^x* ...+1的一个素数 具有一些优美的性质

我们就可以进行NTT[快速数论变换] /斜眼笑/

我们刚刚FFT中用的复平面中的单位根 所以是有小数的

这个样子可不大好因为我们要取模 所以我们有了一个很nb的东西叫做 原根

原根有一些优美的性质 就跟单位根一样 G ^ ((p-1)/i) 就是可以当成单位根使用的数啦 [i|(p-1)]这也就是p为什么要是 2^x *... +1的原因啦 

小姿势:998244353的原根是3

其他详细的细节还是右转百度吧【大雾

扔个代码。

#include<cstdio>
#include<algorithm>
#include<cstring>
#define maxn 300005
#define modn 998244353
#define G 3
#define ll long long
using namespace std;

int q_pow(ll base,ll pow)
{
	ll ans=1;
	while(pow)
	{
		if(pow&1){ans*=base;ans%=modn;}
		base*=base;base%=modn;pow>>=1;
	}
	return (int)ans;
}

int A[maxn],B[maxn],C[maxn];
int l,r[maxn],limit,inv;
void FFT(int *a,int type)
{
	int i,j,k;
	for(i=0;i<limit;i++)
		if(r[i]>i)
			swap(a[r[i]],a[i]);
	for(i=2;i<=limit;i<<=1)
	{
		int mid=i>>1;
		int Wn=q_pow(G,(modn-1)/i);
		if(type)	Wn=q_pow(Wn,(modn-2));
		for(j=0;j<limit;j+=i)
		{
			int w=1;
			for(k=0;k<mid;k++)
			{
				int x=a[j+k],y=a[j+mid+k];
				a[j+k]=x+(ll)w*y%modn;
				if(a[j+k]>=modn)	a[j+k]-=modn;
				a[j+k+mid]=x-(ll)w*y%modn;
				if(a[j+mid+k]<0)	a[j+mid+k]+=modn;
				w=(ll)w*Wn%modn;
			}
		}
	}
	if(type)
	{
		for(i=0;i<limit;i++)
			a[i]=(ll)a[i]*inv%modn;
	}
}

int main()
{
	int n,i,j,k,m,s;
	scanf("%d%d",&n,&m);
	for(i=0;i<=n;i++)	scanf("%d",&A[i]);
	for(i=0;i<=m;i++)	scanf("%d",&B[i]);
	limit=1;while(limit<=(n+m))	limit<<=1,l++;
	for(i=0;i<limit;i++)
		r[i]=(r[i>>1]>>1)|((i&1)<<(l-1));
	inv=q_pow(limit,modn-2);
	FFT(A,0);FFT(B,0);
	for(i=0;i<=limit;i++)
		C[i]=(ll)A[i]*B[i]%modn;
	FFT(C,1);
	for(i=0;i<=(n+m);i++)
		printf("%d\n",C[i]);
	return 0;
}

于是我的姿势还只停留在 FFT/NTT 只是能求个卷积【大雾

然后就有一些奇奇怪怪的东西了=.=+

【奇奇怪怪的东西一】多项式求逆

我们现在有一个多项式f 我们要求一个多项式g满足f * g \equiv 1\ (mod\ x^n) 

这玩意看上去是不是非常奇怪啊【明明就是非常奇怪!

假设我们现在已知一个多项式h 满足 f * h \equiv 1\ (mod\ x^{\lceil \frac{n}{2}\rceil})

我们可以得到g-h\equiv 0\ (mod\ x^{\lceil \frac{n}{2}\rceil})

平方g^2 -2g*h +h^2 \equiv 0 \ (mod\ x^n)

卷上f g - 2h\ + fh^2 \equiv 0\ (mod\ x^{n})

移项g = 2h -fh^2 \ (mod \ x ^n)

递归求解就好啦

边界当然是n=1的时候 g直接取f的常数项的逆元啦qwq。

附代码。

#include<cstdio>
#include<cstring>
#include<algorithm>
#include<cmath>
#define inf 20021225
#define ll long long
#define mdn 998244353
#define G 3
#define mxn 300100
using namespace std;

int rev[mxn];

int ksm(int bs,int mi)
{
	int ans=1;
	while(mi)
	{
		if(mi&1)	ans=(ll)ans*bs%mdn;
		bs=(ll)bs*bs%mdn;mi>>=1;
	}
	return ans;
}
int inv;
void NTT(int *a,int lim,int f)
{
	for(int i=0;i<lim;i++)
		if(rev[i]>i)	swap(a[i],a[rev[i]]);
	for(int k=2;k<=lim;k<<=1)
	{
		int Wn=ksm(G,(mdn-1)/k),mid=k>>1;
		if(f)	Wn=ksm(Wn,mdn-2);
		for(int w=1,i=0;i<lim;i+=k,w=1)
		{
			for(int j=0;j<mid;j++,w=(ll)w*Wn%mdn)
			{
				int x=a[i+j],y=(ll)w*a[i+j+mid]%mdn;
				a[i+j]=(x+y)%mdn;a[i+j+mid]=(x-y+mdn)%mdn;
			}
		}
	}
	if(f)	for(int i=0;i<lim;i++)	a[i]=(ll)a[i]*inv%mdn;
}
int g[mxn],h[mxn],f[mxn];

void poly_inv(int n)
{
	if(n==1)
	{
		g[0]=ksm(f[0],mdn-2);
		//printf("%d\n",g[0]);
		return;
	}
	poly_inv((n+1)>>1);
	int lim=1,l=0;
	while(lim<(n<<1))	lim<<=1,l++;
	for(int i=0;i<lim;i++)
		rev[i]=(rev[i>>1]>>1)|((i&1)<<(l-1));
	inv=ksm(lim,mdn-2);
	for(int i=0;i<n;i++)	h[i]=f[i];
	for(int i=n;i<lim;i++)	h[i]=0;
	NTT(h,lim,0);NTT(g,lim,0);
	for(int i=0;i<lim;i++)
		g[i]=(ll)(2ll-(ll)g[i]*h[i]%mdn+mdn)%mdn*g[i]%mdn;
	NTT(g,lim,1);
	for(int i=n;i<lim;i++)	g[i]=0;
}

int main()
{
	int n;
	scanf("%d",&n);
	for(int i=0;i<n;i++)	scanf("%d",&f[i]);
	poly_inv(n);
	for(int i=0;i<n;i++)	printf("%d ",g[i]);
	return 0;
}

【奇奇怪怪的东西二】多项式对数函数

看上去是不是很高大上!【实则蠢得一批

对于多项式 f 求g=ln f

这之前我们科普一点求导和积分的小姿势

对于一个普通多项式

求导f'(x) = \sum_{i=0}^{n-1} (i+1)a_{i+1} x^i

积分\int x^a dx = \frac{1}{a+1} x^{a+1}

两个过程都很像哒 就是反过来做而已233

ln x求导是 1/x

复合函数求导(g(f(x)))'= g'(f(x))f(x)

然后ln f的求导

(ln f(x))' =\frac{1}{f(x)}*f'(x)

直接多项式求逆然后求导再积分回去就好啦qwq

代码等我一哈【不咕不咕必定不可能咕

持续更新!= =+

猜你喜欢

转载自blog.csdn.net/hanyuweining/article/details/84936710
今日推荐