多项式全家桶

warning:  目前为止本博客仍是半半半半半成品。

以下是本文参考以及推荐阅读的相关博客%%%
p.s. 博客都写得很好!没懂是蒟蒻个人问题

点击查看相关博客Link

OI-wiki 快速傅里叶变换:难得感觉有点抽象。

OI - wiki 拉格朗日插值:配合其他博客食用更佳。

傅里叶变换(FFT)学习笔记:从前置芝士讲起的详细学习笔记。

小学生都能看懂的FFT!!!:比较通俗并且不枯燥的讲解。

快速傅里叶变换(FFT)详解:证明很详细。

多项式初步与傅里叶变换(Fourier Transform) : 清晰但是初看有一丢没看懂,可以康康迭代部分。

多项式 I:拉格朗日插值与快速傅里叶变换:感觉已经算是全家桶了()通过这篇学会了拉插!

从拉插到快速插值求值:超级详细的插值笔记。


概念

  • DFT: 系数表达转换为点值表达。

  • IDFT: 点值表达转换为系数表达。( DFT的逆运算 )


拉格朗日插值

考虑构造 fi(x) , 使得 fi(xi)=1fi(xj)=0 (ji)
首先考虑满足后者,则有: fi(x)=ij(xxj)
再考虑前者,则在两边同乘 yifi(xi) ,可以得到:fi(x)=yijixxjxixj
多项式 f(x) 即为:

f(x)=i=1nyifi(x)=i=1nyijixxjxixj

注意最后一起算分母的逆元,而不是分别求解。
模版题:luogu P4781

点击查看代码
#include<bits/stdc++.h>
#define cs const
#define il inline
#define LL long long
#define pc(i) putchar(i)
#define fo(i,j,k) for(int i=(j);(i)<=(k);++(i))
#define of(i,j,k) for(int i=(j);(i)>=(k);--(i))
using namespace std;
mt19937 srd(time(0));
cs int inf=0x3f3f3f3f,Mod=998244353,N=2003;
int FL,CH;
template<typename T> bool in(T&a)
{
    for(FL=1;!isdigit(CH)&&CH!=EOF;CH=getchar())
        if(CH=='-')FL=-1;
    for(a=0;isdigit(CH);CH=getchar())
        a=a*10+CH-'0';
    return a*=FL,CH==EOF?0:1;
}
template<typename T,typename...Args>
int in(T&a,Args&...args){return in(a)+in(args...);}
void wt(int x){if(x<0)x=-x,pc('-');if(x>9)wt(x/10);pc(x%10|48);}
int x[N],y[N],n,k; LL ans;
il int qwq(cs int x,cs int y){return (x-y+Mod)%Mod;}
il int qpow(int b,int p)
{
	int r=1;
	while(p>0)
	{
		if(p&1) r=1ll*r*b%Mod;
		p>>=1,b=1ll*b*b%Mod;
	}
	return r;
}
il int inv(cs int x){return qpow(x,Mod-2);} 
signed main()
{
	in(n,k); fo(i,1,n) in(x[i],y[i]);
	fo(i,1,n) 
	{
		int s1=y[i]%Mod,s2=1; 
		fo(j,1,n) if(i!=j)
			s1=1ll*s1*qwq(k,x[j])%Mod,s2=1ll*s2*qwq(x[i],x[j])%Mod;
		ans=(ans+1ll*s1*inv(s2)%Mod)%Mod;
	}
	wt(ans);
    return 0;
}

横坐标是连续整数的拉格朗日插值

省流:可以 O(n)
横坐标为 1,,n的插值公式:

f(x)=i=1nyiijxxjxixj=i=1nyijixjij=i=1nyij=1n(xj)xi(1)ni(i1)!(ni)!=i=1nyij=1n(xj)(xi)(1)ni(i1)!(ni)!

例题:CF622F The Sum of the k-th Powers

题目大意:求 i=1nik,n109,k106

答案是一个 k+1 次的多项式,找 k+2 个点带进去拉插,证明在这里找。
预处理阶乘和前后缀积。

点击查看代码
#include<bits/stdc++.h>
#define cs const
#define il inline
#define LL long long
#define pc(i) putchar(i)
#define fo(i,j,k) for(int i=(j);(i)<=(k);++(i))
#define of(i,j,k) for(int i=(j);(i)>=(k);--(i))
using namespace std;
mt19937 srd(time(0));
cs int inf=0x3f3f3f3f,Mod=1e9+7,N=1e6+7;
int FL,CH;
template<typename T> bool in(T&a)
{
    for(FL=1;!isdigit(CH)&&CH!=EOF;CH=getchar())
        if(CH=='-')FL=-1;
    for(a=0;isdigit(CH);CH=getchar())
        a=a*10+CH-'0';
    return a*=FL,CH==EOF?0:1;
}
template<typename T,typename...Args>
int in(T&a,Args&...args){return in(a)+in(args...);}
void wt(int x){if(x<0)x=-x,pc('-');if(x>9)wt(x/10);pc(x%10|48);}
int n,k,ans,fac[N],L[N],R[N],y,num,den;
il int qpow(int b,int p)
{
	int re=1;
	while(p>0)
	{
		if(p&1) re=1ll*re*b%Mod;
		p>>=1,b=1ll*b*b%Mod;
	}
	return re;
}
il int inv(cs int x){ return qpow(x,Mod-2); }
signed main()
{
	in(n,k),L[0]=R[k+3]=fac[0]=1;
	fo(i,1,k+2) L[i]=1ll*L[i-1]*(n-i)%Mod,fac[i]=1ll*fac[i-1]*i%Mod;
	of(i,k+2,1) R[i]=1ll*R[i+1]*(n-i)%Mod;
	fo(i,1,k+2)
	{
		y=(y+qpow(i,k))%Mod,num=1ll*L[i-1]*R[i+1]%Mod,
		den=1ll*fac[i-1]*((k-i)&1?-1:1)*fac[k+2-i]%Mod;
		ans=(ans+1ll*y*num%Mod*inv(den)%Mod)%Mod; 
	}
	wt((ans+Mod)%Mod); 
    return 0;
}

FFT

复数乘法

(a+bi)(c+di)=ac+adi+bci+bdi2=ac+adi+bcibd=(acbd)+(bc+ad)i

单位根

  • ωnk=cosk×2πn+isink×2πn
  • ω2n2k=ωnk
  • ωn0=ωnn=1

快速傅里叶变换

A(x)=a0+a1×x+a2×x2+a3×x3+a4×x4++an2×xn2+an1×xn1

按照下标的奇偶性分类:

A(x)=(a0+a2×x2+a4×x4++an2×xn2)+(a1×x+a3×x3+a5×x5an1×xn1)

设:

A1(x)=a0+a2×x+a4×x2++an2×xn21

A2(x)=a1+a3×x+a5×x2++an1×xn21

则有: A(x)=A1(x2)+xA2(x2)

ωnk(k<n2) 代入:A(ωnk)=A1(ωn2k)+ωnkA2(ωn2k)=A1(ωn2k)+ωnkA2(ωn2k)

ωnk+n2 代入: A(ωnk+n2)=A1(ωn2k+n)+ωnk+n2A2(ωn2k+n)=A1(ωn2k)ωnkA2(ωn2k)

快速傅里叶逆变换

点值表示法转化为系数表示法。

(y0,y1,,yn1)(a0,a1,,an1) 的点值表示法。

设有一向量 (c0,c1,,cn1) 满足 ck=i=0n1yi(ωnk)i ,即多项式 B(x)=y0+y1×x+y2×x2++yn1×xn1ωn0,ωn1,ωn2,,ωn(n1) 处的点值表示。

ck=i=0n1yi(ωnk)i=i=0n1(j=0n1aj(ωni)j)(ωnk)i=i=0n1(j=0n1aj(ωnj)i)(ωnk)i=i=0n1(j=0n1aj(ωnj)i(ωnk)i)=i=0n1j=0n1aj(ωnj)i(ωnk)i=i=0n1j=0n1aj(ωnjk)i=j=0n1aj(i=0n1(ωnjk)i)

S(x)=i=0n1xi,将 ωnk 代入:S(ωnk)=1+(ωnk)+(ωnk)2++(ωnk)n1

k0 时,同乘 ωnkωnkS(ωnk)=ωnk+(ωnk)+(ωnk)2+(ωnk)3++(ωnk)n

上下两式相减可得:

ωnkS(ωnk)S(ωnk)=(ωnk)n1S(ωnk)=(ωnk)n1ωnk1S(ωnk)=(ωnn)k1ωnk1S(ωnk)=11ωnk1

因此,k0时,S(ωnk)=0,而 k=0S(ωn0)=n

对于

ck=j=0n1aj(i=0n1(ωnjk)i)

jk 时,值为 0 ,而 j=k 时值为 n

因此,ck=nakak=ckn

迭代实现

a0a1a2a3|a4a5a6a7a0a2|a4a6|a1a3a5a7a0|a4|a2|a6|a1|a5|a3|a7

000,001,010,011|100,101,110,111|000,010|100,110|001,011|101,111000|100|010|110|001|101|011|111

在原序列中 ii2 的关系是 : i 可以看做是 i2 的二进制上的每一位左移一位得来,那么在反转后的数组中就需要右移一位,同时特殊处理一下奇数。
记为 rev[i]=(rev[i>>1]>>1)|((rev[i]&1)<<(bit1)),bit=log2n

FFT常数优化论文
P3803 【模板】多项式乘法(FFT)

点击查看代码
#include<bits/stdc++.h>
#define cs const
#define il inline
#define fo(i,j,k) for(int i=(j);(i)<=(k);++(i))
#define of(i,j,k),for(int i=(j);(i)>=(k);--(i))
#define LL long long
using namespace std;
cs int N=3e6+9;
cs double pi=acos(-1);
int FL,CH;
template<typename T>bool in(T&a)
{
    for(FL=1;!isdigit(CH)&&CH!=EOF;CH=getchar())
        if(CH=='-')FL=-1;
    for(a=0;isdigit(CH);CH=getchar())
        a=a*10+CH-'0';
    return a*=FL,CH==EOF?0:1;
}
template<typename T,typename...Args>
int in(T&a,Args&...args){return in(a)+in(args...);}
struct Complex
{
    double x,y;
    Complex operator+(const Complex &a) const
    { return {x+a.x,y+a.y}; }
    Complex operator-(const Complex &a) const
    { return {x-a.x,y-a.y}; }
    Complex operator*(const Complex &a) const
    { return {x*a.x-y*a.y,x*a.y+y*a.x}; }
}A[N],B[N];
int rev[N],bit,tot,n,m;
il void FFT(Complex a[],int inv)
{
    fo(i,0,tot-1) if(i<rev[i]) swap(a[i],a[rev[i]]); //求出要迭代的序列
    for(int mid=1;mid<tot;mid<<=1)//待合并区间的长度的一半
    {
        Complex w1=(Complex){cos(pi/mid),inv*sin(pi/mid)};//单位根
        for(int i=0;i<tot;i+=mid*2)
        {
            Complex wk=(Complex){1,0};
            for(int j=0;j<mid;++j,wk=wk*w1)//枚举左半部分
            {
                Complex x=a[i+j],y=wk*a[i+j+mid];
                a[i+j]=x+y,a[i+j+mid]=x-y;
            }
        }
    }
}
signed main()
{
    in(n,m);
    fo(i,0,n) scanf("%lf",&A[i].x);
    fo(i,0,m) scanf("%lf",&B[i].x);
    while((1<<bit)<n+m+1) ++bit; //bit=ceil(log_2^n)
    tot=1<<bit;
    fo(i,0,tot-1) rev[i]=(rev[i>>1]>>1)|((i&1)<<(bit-1));
    FFT(A,1),FFT(B,1);
    fo(i,0,tot-1) A[i]=A[i]*B[i];
    FFT(A,-1);
    fo(i,0,n+m) printf("%d ",(int)(A[i].x/tot+0.5));
    return 0;
}

P1919 【模板】A*B Problem 升级版(FFT 快速傅里叶变换)

点击查看代码
#include<bits/stdc++.h>
#define cs const
#define il inline
#define fo(i,j,k) for(int i=(j);(i)<=(k);++(i))
#define of(i,j,k) for(int i=(j);(i)>=(k);--(i))
#define LL long long
using namespace std;
cs int N=3e6+9;
cs double pi=acos(-1);
int FL,CH;
template<typename T>bool in(T&a)
{
    for(FL=1;!isdigit(CH)&&CH!=EOF;CH=getchar())
        if(CH=='-')FL=-1;
    for(a=0;isdigit(CH);CH=getchar())
        a=a*10+CH-'0';
    return a*=FL,CH==EOF?0:1;
}
template<typename T,typename...Args>
int in(T&a,Args&...args){return in(a)+in(args...);}
struct Complex
{
    double x,y;
    Complex operator+(cs Complex &a) cs
    { return {x+a.x,y+a.y}; }
    Complex operator-(cs Complex &a) cs
    { return {x-a.x,y-a.y}; }
    Complex operator*(cs Complex &a) cs
    { return {a.x*x-y*a.y,x*a.y+y*a.x}; }
}A[N],B[N];
char ca[N],cb[N];
int rev[N],bit,tot,n,m,ans[N],id;
il void FFT(Complex a[],cs int inv)
{
    fo(i,0,tot-1) if(i<rev[i]) swap(a[i],a[rev[i]]);
    for(int mid=1;mid<tot;mid<<=1)
    {
        Complex w1=(Complex){cos(pi/mid),inv*sin(pi/mid)};
        for(int i=0;i<tot;i+=mid*2)
        {
            Complex wk=(Complex){1,0};
            for(int j=0;j<mid;++j,wk=wk*w1)
            {
                Complex x=a[i+j],y=wk*a[i+j+mid];
                a[i+j]=x+y,a[i+j+mid]=x-y;
            }
        }
    }
}
signed main()
{
    scanf("%s%s",ca,cb);
    n=strlen(ca)-1,m=strlen(cb)-1;
    fo(i,0,n) A[i].x=(ca[n-i]^48);
    fo(i,0,m) B[i].x=(cb[m-i]^48);
    while((1<<bit)<n+m-1) ++bit; 
    tot=1<<bit;
    fo(i,0,tot-1) rev[i]=(rev[i>>1]>>1)|((i&1)<<(bit-1));
    FFT(A,1),FFT(B,1);
    fo(i,0,tot-1) A[i]=A[i]*B[i];
    FFT(A,-1); 
    for(int i=0,x=0;i<tot||x;++i)
        x+=A[i].x/tot+0.5,ans[++id]=x%10,x/=10;
    while(id>2&&(!ans[id-1])) --id;
    of(i,id-1,1) printf("%d",ans[i]);
    return 0;
}
posted @   Bertidurlah  阅读(32)  评论(3编辑  收藏  举报
相关博文:
阅读排行:
· 地球OL攻略 —— 某应届生求职总结
· 周边上新:园子的第一款马克杯温暖上架
· Open-Sora 2.0 重磅开源!
· 提示词工程——AI应用必不可少的技术
· .NET周刊【3月第1期 2025-03-02】
点击右上角即可分享
微信分享提示