从拉插到快速插值求值
command_block · · 个人记录
标题原来很长的,简写却之后变得很拗口
老是忘记这个东西,干脆写个文章好了……
为了显示点值和未知数的区别,自由元
广告 : 多项式计数杂谈
1.拉格朗日插值
P4781 【模板】拉格朗日插值
-
题意 :
有
n 个不同的点值(x_i,y_i) ,让你求出一个低于n 次的多项式,满足经过上述n 个点。
首先由代数基本定理,
对于第
①
也就是说,这个多项式专门用来对付第
那么
考虑先用因式定理构造
然后还要令满足 ①
这些多项式均不超过
下面我们来看看一些具体问题:
CF622F The Sum of the k-th Powers
这是一道很好的例题,题意不再赘述。
容易感性认知,自然数
为啥嘞?数列微积分
你可以这样理解,
我们随便找
( 为了方便,下面
那么直接套用上述插值,即可达到
我们观察这个问题的特点 : 点值都是连续的,而且不需要真的求出这个多项式。
变一下:
分母上面的东西维护一个前缀积和后缀积即可。
这样子就可以
比隔壁O(mlogm)MTT第二类斯特林数不知高到哪里去了!
当然,我这个蒟蒻比较懒,所以要线性筛代码还是去膜拜别的大佬吧……
Code:
#include<cstdio>
#define ll long long
#define MaxN 1005000
#define mod 1000000007
using namespace std;
ll powM(ll a,ll t){
ll ans=1;
while(t){
if (t&1)ans=ans*a%mod;
a=a*a%mod;t>>=1;
}return ans;
}
ll n,m,ans,y[MaxN],lf[MaxN],rf[MaxN],ifac[MaxN];
int main()
{
scanf("%I64d%I64d",&n,&m);
for (int i=1;i<=m+2;i++)
y[i]=(y[i-1]+powM(i,m))%mod;
m+=2;
ifac[0]=ifac[1]=1;
for (int i=2;i<=m;i++)
ifac[i]=ifac[mod%i]*(mod-mod/i)%mod;
for (int i=2;i<=m;i++)
ifac[i]=ifac[i]*ifac[i-1]%mod;
lf[0]=1;
for (int i=1;i<=m;i++)
lf[i]=lf[i-1]*(n-i)%mod;
rf[m+1]=1;
for (int i=m;i;i--)
rf[i]=rf[i+1]*(n-i)%mod;
for (int i=1;i<=m;i++)
ans=(ans+y[i]*lf[i-1]%mod*
rf[i+1]%mod*ifac[i-1]%mod*
((m-i)&1 ? mod-ifac[m-i] : ifac[m-i]))%mod;
printf("%I64d",(ans+mod)%mod);
return 0;
}
上面的例题是不需要具体的多项式系数的,一旦需要怎么办呢?
还是看着式子 :
设
我们先计算
具体细节请见代码,验板子可以去下方模板的
复杂度
#include<algorithm>
#include<cstdio>
#define mod 998244353
#define Maxn 5050
#define ll long long
using namespace std;
ll powM(ll a,ll t=mod-2){
ll ans=1;
while(t){
if(t&1)ans=ans*a%mod;
a=a*a%mod;t>>=1;
}return ans;
}
int n;
ll x[Maxn],y[Maxn],c[Maxn],fs[Maxn],g[Maxn],f[Maxn];
int main()
{
scanf("%d",&n);
for (int i=0;i<n;i++)
scanf("%lld%lld",&x[i],&y[i]);
for (int i=0;i<n;i++){
c[i]=1;
for (int j=0;j<n;j++)
if (i!=j)
c[i]=c[i]*(x[i]-x[j])%mod;
c[i]=powM(c[i])*y[i]%mod;
}
fs[0]=1;
for (int i=0;i<n;i++){
for (int j=n;j;j--)
fs[j]=(fs[j-1]+fs[j]*(mod-x[i]))%mod;
fs[0]=fs[0]*(mod-x[i])%mod;
}
for (int i=0;i<n;i++){
ll buf=powM(mod-x[i]);
g[0]=fs[0]*buf%mod;
for (int j=1;j<n;j++)
g[j]=(fs[j]-g[j-1])*buf%mod;
for (int j=0;j<n;j++)
f[j]=(f[j]+c[i]*g[j])%mod;
}
for (int i=0;i<n;i++)
printf("%lld ",(f[i]+mod)%mod);
return 0;
}
以下内容可能引起不适……
请先掌握 NTT与多项式全家桶 ,Ln+Exp可以先忽略。
P5050 【模板】多项式多点求值
题意 : 给一个多项式和
首先,设
那么,当代入
代码呢,不那么好写。先要分治NTT先预处理出每一层的
这是我第一次写分治FFT啊……我这种老是爆边界的人自然会把代码写的很丑啦TAT
此外这个模板毒瘤卡常数,记得范围小的时候采用暴力代入减小常数(重要),NTT一定要弄个常数小的来。
#include<algorithm>
#include<cstring>
#include<cstdio>
#define ll long long
#define ull unsigned long long
#define clr(f,n) memset(f,0,sizeof(int)*(n))
#define cpy(f,g,n) memcpy(f,g,sizeof(int)*(n))
using namespace std;
const int _G=3,mod=998244353,Maxn=66666;
int read(){
int X=0;char ch=0;
while(ch<48||ch>57)ch=getchar();
while(ch>=48&&ch<=57)X=X*10+(ch^48),ch=getchar();
return X;
}
ll powM(ll a,ll t=mod-2){
ll ans=1;
while(t){
if(t&1)ans=ans*a%mod;
a=a*a%mod;t>>=1;
}return ans;
}
const int invG=powM(_G);
int tr[Maxn<<1],tf;
void tpre(int n){
if (tf==n)return ;tf=n;
for(int i=0;i<n;i++)
tr[i]=(tr[i>>1]>>1)|((i&1)?n>>1:0);
}
void NTT(int *g,bool op,int n)
{
static ull f[Maxn<<1],w[Maxn]={1};
tpre(n);
for (int i=0;i<n;i++)f[i]=(((ll)mod<<5)+g[tr[i]])%mod;
for(int l=1;l<n;l<<=1){
ull tG=powM(op?_G:invG,(mod-1)/(l+l));
for (int i=1;i<l;i++)w[i]=w[i-1]*tG%mod;
for(int k=0;k<n;k+=l+l)
for(int p=0;p<l;p++){
int tt=w[p]*f[k|l|p]%mod;
f[k|l|p]=f[k|p]+mod-tt;
f[k|p]+=tt;
}
if (l==(1<<10))
for (int i=0;i<n;i++)f[i]%=mod;
}if (!op){
ull invn=powM(n);
for(int i=0;i<n;++i)
g[i]=f[i]%mod*invn%mod;
}else for(int i=0;i<n;++i)g[i]=f[i]%mod;
}
void px(int *f,int *g,int n)
{for(int i=0;i<n;++i)f[i]=1ll*f[i]*g[i]%mod;}
int _g1[Maxn<<1];
#define sav _g1
void times(int *f,int *g,int len,int lim)
{
int n=1;while(n<len+len)n<<=1;
cpy(sav,g,n);clr(sav+len,n-len);
NTT(f,1,n);NTT(sav,1,n);
px(f,sav,n);NTT(f,0,n);
clr(f+lim,n-lim);clr(sav,n);
}
void invp(int *f,int m)
{
static int w[Maxn<<1],r[Maxn<<1];
int n;for (n=1;n<m;n<<=1);
w[0]=powM(f[0]);
for (int len=2;len<=n;len<<=1){
for (int i=0;i<(len>>1);i++)
r[i]=(w[i]<<1)%mod;
cpy(sav,f,len);
NTT(w,1,len<<1);px(w,w,len<<1);
NTT(sav,1,len<<1);px(w,sav,len<<1);
NTT(w,0,len<<1);clr(w+len,len);
for (int i=0;i<len;i++)
w[i]=(r[i]-w[i]+mod)%mod;
}cpy(f,w,m);clr(sav,n+n);clr(w,n+n);clr(r,n+n);
}
void fan(int *f,int m){
cpy(sav,f,m);
for (int i=0;i<m;i++)f[i]=sav[m-i-1];
clr(sav,m);
}
#undef sav
void savtimes(int *f,int *g,int len1,int len2,int lim)
{
static int s1[Maxn<<1],s2[Maxn<<1];
cpy(s1,f,len1);cpy(s2,g,len2);
times(s1,s2,max(len1,len2),lim);
cpy(f,s1,lim);clr(s1,lim);clr(s2,len2);
}
void mof(int *f,int *g,int n,int m)
{
static int q[Maxn<<1],t[Maxn<<1];
int L=n-m+1;
fan(g,m);cpy(q,g,L);fan(g,m);
fan(f,n);cpy(t,f,L);fan(f,n);
invp(q,L);times(q,t,L,L);fan(q,L);
savtimes(q,g,L,m,m-1);
for (int i=0;i<m-1;i++)
f[i]=(f[i]-q[i]+mod)%mod;
clr(f+m-1,L);clr(q,n);clr(t,n);
}
int gl[17][Maxn<<1];
void qfpre(int lev,int l,int r,int *x)
{
if (l==r){
gl[lev][l<<1]=mod-x[l];
gl[lev][l<<1|1]=1;
return ;
}int mid=(l+r)>>1;
qfpre(lev+1,l,mid,x);
qfpre(lev+1,mid+1,r,x);
cpy(&gl[lev][l<<1],&gl[lev+1][l<<1],mid-l+2);
savtimes(&gl[lev][l<<1],&gl[lev+1][(mid+1)<<1],mid-l+2,r-mid+1,r-l+2);
}
int _s1[Maxn<<1],_s2[Maxn<<1];
#define s1 _s1
#define s2 _s2
void _queryf(int lev,int l,int r,int *f,int *x,int *y)
{
if (r-l<=350){
for (int i=l;i<=r;i++){
ll buf=1;
for (int j=l+l;j<l+r+1;j++){
y[i]=(y[i]+buf*f[j])%mod;
buf=buf*x[i]%mod;
}
}return ;
}int mid=(l+r)>>1;
cpy(s1,&f[l<<1],r-l+1);
mof(s1,&gl[lev+1][l<<1],r-l+1,mid-l+2);
cpy(s2,&f[l<<1],r-l+1);
mof(s2,&gl[lev+1][(mid+1)<<1],r-l+1,r-mid+1);
for (int i=(l<<1);i<(r<<1|1);i++)f[i]=0;
cpy(&f[l<<1],_s1,mid-l+1);
cpy(&f[(mid+1)<<1],_s2,r-mid);
clr(s1,r-l+1);clr(s2,r-l+1);
_queryf(lev+1,l,mid,f,x,y);
_queryf(lev+1,mid+1,r,f,x,y);
}
#undef s1
#undef s2
void queryf(int *f,int *x,int *y,int n,int m)
{
qfpre(0,0,m-1,x);
if (n>m)mof(f,gl[0],n,m+1);
_queryf(0,0,m-1,f,x,y);
}
int n,m,f[Maxn<<1],x[Maxn],y[Maxn];
int main()
{
n=read()+1;m=read();
for (int i=0;i<n;i++)f[i]=read();
for (int i=0;i<m;i++)x[i]=read();
queryf(f,x,y,n,m);
for (int i=0;i<m;i++)printf("%d\n",y[i]);
return 0;
}
先来缓口气(?),做做这道卡常题:P5282 【模板】快速阶乘算法
-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-
P5158 【模板】多项式快速插值
显然直接暴力
拉格朗日插值公式还是要拿过来用的:
给变得好看一点:
-
首先算每个
\prod\limits_{i=1,k≠i}^m(x_k-x_i) 我们学了那么多多项式技巧,不能被这么一个常数骗过去啊!
我们设
G(X)=\prod\limits_{i=1}^m(X-x_i) ,这可以分治NTT求出来。这个式子就是$\dfrac{0}{0}$形式的**洛必达**,由$\lim\limits_{X→x_k}G(X)=\lim\limits_{X→x_k}(X-x_k)=0 那么
\lim\limits_{X→x_k}\dfrac{G(X)}{(X-x_k)}=\lim\limits_{X→x_k}\dfrac{G'(X)}{(X-x_k)'} (上下同时求导)=\lim\limits_{X→x_k}G'(x)=G'(x_k) 那么我们写个多项式多点求值,就能从
G'(x) 里弄出上述插值常数了。
现在就能得到
设
这部分考虑分治,设
令
那么就变成两个规模为一半的子问题了,分治NTT上!
复杂度
如何减小常数呢?
此外,分治范围小到一定程度的时候可以暴力拉插公式(这个优化我没写,因为其实求值才是瓶颈)。
卡到最后当然是预处理单位根啦。
#include<algorithm>
#include<cstdio>
#define mod 998244353
#define G 3
#define Maxn 140000
#define ll long long
using namespace std;
inline int read()
{
register int X=0;
register char ch=0;
while(ch<48||ch>57)ch=getchar();
while(ch>=48&&ch<=57)X=X*10+(ch^48),ch=getchar();
return X;
}
int r[Maxn<<1],rn;
ll invn,invG;
ll powM(ll a,ll t=mod-2)
{
ll ans=1;
while(t){
if(t&1)ans=ans*a%mod;
a=a*a%mod;
t>>=1;
}return ans;
}
//f=g
inline void cop(ll *f,ll *g,int len)
{for (int i=0;i<len;i++)f[i]=g[i];}
ll pG[2][19][Maxn<<2];
void preG()
{
for(int d=1;d<=17;d++){
ll buf=powM(G,(mod-1)/(1<<d));
pG[1][d][0]=1;
for(int i=1;i<(1<<d);i++)
pG[1][d][i]=pG[1][d][i-1]*buf%mod;
}invG=powM(G);
for(int d=1;d<=17;d++){
ll buf=powM(invG,(mod-1)/(1<<d));
pG[0][d][0]=1;
for(int i=1;i<(1<<d);i++)
pG[0][d][i]=pG[0][d][i-1]*buf%mod;
}
}
void NTT(ll *f,int n,int op)
{
for (int i=0;i<n;i++)
if (r[i]<i)swap(f[r[i]],f[i]);
ll *buf;
for (int p=2,j=1;p<=n;p<<=1,j++){
int len=p>>1;
for (int k=0;k<n;k+=p){
buf=pG[op][j];
for (int i=k;i<k+len;i++,buf++){
int sav=f[len+i]*(*buf)%mod;
f[len+i]=f[i]-sav;
if (f[len+i]<0)f[len+i]+=mod;
f[i]=f[i]+sav;
if (f[i]>=mod)f[i]-=mod;
}
}
}
}
ll _g[Maxn<<1];//f*=g
void times(ll *f,ll *gg,int len1,int len2,int limit)
{
int n=1;for(;n<len1+len2;n<<=1);
ll *g=_g;
for (int i=0;i<len2;i++)g[i]=gg[i];
if (rn!=n){
rn=n;
for(int i=0;i<n;i++)
r[i]=(r[i>>1]>>1)|((i&1)?n>>1:0);
}NTT(f,n,1);NTT(g,n,1);
for(int i=0;i<n;++i)f[i]=f[i]*g[i]%mod;
NTT(f,n,0);invn=powM(n);
for(int i=0;i<limit;++i)f[i]=f[i]*invn%mod;
for(int i=limit;i<n;++i)f[i]=0;
for (int i=0;i<n;i++)g[i]=0;
}
ll _sav[Maxn<<1];//f*=g
inline void savtimes(ll *f,ll *g,int len1,int len2,int limit)
{
for (int i=0;i<len1;i++)_sav[i]=f[i];
times(_sav,g,len1,len2,limit);
for(int i=0;i<limit;++i)f[i]=_sav[i];
for(int i=0;i<limit;++i)_sav[i]=0;
}
void dao(ll *f,int n)
{for (int i=1;i<n;i++)f[i-1]=f[i]*i%mod;f[n]=0;}
ll _r[Maxn<<1],_rr[Maxn<<1];
void invp(ll *f,int len)
{
ll *r=_r,*rr=_rr;
int n=1;for(;n<len;n<<=1);
rr[0]=powM(f[0]);
for (int len=2;len<=n;len<<=1){
for (int i=0;i<len;i++)
r[i]=(rr[i]<<1)%mod;
times(rr,rr,len>>1,len>>1,len);
times(rr,f,len,len,len);
for (int i=0;i<len;i++)
rr[i]=(r[i]-rr[i]+mod)%mod;
}for (int i=0;i<len;i++)f[i]=rr[i];
for (int i=0;i<n;i++)r[i]=rr[i]=0;
}
inline void fan(ll *f,int m)
{
for (int i=0;i<m;i++)_sav[i]=f[i];
for (int i=0;i<m;i++)f[i]=_sav[m-i-1];
for (int i=0;i<m;i++)_sav[i]=0;
}
ll _q[Maxn<<1],_t[Maxn<<1];//f%=g
void mof(ll *f,ll *g,int n,int m)
{
ll *q=_q,*t=_t;
fan(g,m);cop(q,g,n-m+1);fan(g,m);
invp(q,n-m+1);
fan(f,n);cop(t,f,n-m+1);fan(f,n);
times(q,t,n-m+1,n-m+1,n-m+1);
fan(q,n-m+1);
times(q,g,n-m+1,m,n);
for (int i=0;i<m-1;i++)f[i]=(f[i]-q[i]+mod)%mod;
for (int i=m-1;i<n;i++)f[i]=0;
for (int i=0;i<n;i++)q[i]=t[i]=0;
}
ll gl[18][Maxn<<1];
void qfpre(int lev,int l,int r,ll *x)
{
if (l==r){
gl[lev][l<<1]=mod-x[l];
gl[lev][l<<1|1]=1;
return ;
}int mid=(l+r)>>1;
qfpre(lev+1,l,mid,x);
qfpre(lev+1,mid+1,r,x);
cop(&gl[lev][l<<1],&gl[lev+1][l<<1],mid-l+2);
savtimes(&gl[lev][l<<1],&gl[lev+1][(mid+1)<<1],mid-l+2,r-mid+1,r-l+2);
}
ll _s1[Maxn<<1],_s2[Maxn<<1];
void queryf(int lev,int l,int r,ll *f,ll *x,ll *y)
{
if (r-l<=800){
for (int i=l;i<=r;i++){
register ll buf=1;
for (int j=l+l;j<l+r+1;j++){
y[i]=(y[i]+buf*f[j])%mod;
buf=buf*x[i]%mod;
}
}return ;
}int mid=(l+r)>>1;
cop(_s1,&f[l<<1],r-l+1);
mof(_s1,&gl[lev+1][l<<1],r-l+1,mid-l+2);
cop(_s2,&f[l<<1],r-l+1);
mof(_s2,&gl[lev+1][(mid+1)<<1],r-l+1,r-mid+1);
for (int i=(l<<1);i<(r<<1|1);i++)f[i]=0;
cop(&f[l<<1],_s1,mid-l+1);
cop(&f[(mid+1)<<1],_s2,r-mid);
for (int i=0;i<r-l+1;i++)_s1[i]=_s2[i]=0;
queryf(lev+1,l,mid,f,x,y);
queryf(lev+1,mid+1,r,f,x,y);
}
void polf(int lev,int l,int r,ll *f,ll *x,ll *v)
{
if (l==r){f[l<<1]=v[l];return ;}
int mid=(l+r)>>1;
polf(lev+1,l,mid,f,x,v);
polf(lev+1,mid+1,r,f,x,v);
cop(_s1,&f[l<<1],mid-l+1);
times(_s1,&gl[lev+1][(mid+1)<<1],mid-l+1,r-mid+1,r-l+1);
cop(_s2,&f[(mid+1)<<1],r-mid);
times(_s2,&gl[lev+1][l<<1],r-mid,mid-l+2,r-l+1);
for (int i=(l<<1);i<(r<<1|1);i++)f[i]=0;
for (int i=0;i<r-l+1;i++){
f[i+(l<<1)]=(_s1[i]+_s2[i])%mod;
_s1[i]=_s2[i]=0;
}
}
int n,m;
ll x[Maxn],y[Maxn],y2[Maxn],g[Maxn<<1],f[Maxn<<1];
int main()
{
preG();
m=read();
for (int i=0;i<m;i++)
{x[i]=read();y[i]=read();}
qfpre(0,0,m-1,x);
cop(g,gl[0],m+1);
dao(g,m+1);
queryf(0,0,m-1,g,x,y2);
for (int i=0;i<m;i++)y[i]=y[i]*powM(y2[i])%mod;
polf(0,0,m-1,f,x,y);
for (int i=0;i<m;i++)printf("%lld ",f[i]);
return 0;
}
快速插值非常难写,而且跑得很慢。
很多时候点值可以自己找,为了计算方便,通常找
这时的插值虽然不用写求值了,直接连乘搞搞就好,但是两个