luogu P4725 多项式对数函数 (模板题、FFT、多项式求逆、求导和积分)

本文介绍了一种解决多项式对数问题的高效算法。通过使用多项式求逆和链式法则,可以在O(nlogn)的时间复杂度内求解B(x)≡lnA(x)(mod xn)。文章详细讲解了求导、积分、多项式求逆等关键概念,并提供了完整的代码实现。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

题目链接: https://www.luogu.org/problemnew/show/P4725

题目大意: 给定一个 n n n次多项式 A ( x ) A(x) A(x), 求一个 n n n次多项式 B ( x ) B(x) B(x)满足 B ( x ) ≡ ln ⁡ A ( x ) ( m o d    x n ) B(x)\equiv \ln A(x) (\mod x^n) B(x)lnA(x)(modxn)

题解: 神数学模板题……
数学真奇妙!
前驱知识
导数、积分相关
幂函数的求导
f ( x ) = x n , f ′ ( x ) = n x n − 1 f(x)=x^n, f'(x)=nx^{n-1} f(x)=xn,f(x)=nxn1
和的导数等于导数的和
( f + g ) ′ ( x ) = f ′ ( x ) + g ′ ( x ) (f+g)'(x)=f'(x)+g'(x) (f+g)(x)=f(x)+g(x)
一般多项式的求导
f ( x ) = ∑ i = 0 n − 1 a i x i , f ′ ( x ) = ∑ i = 0 n − 2 ( i + 1 ) a i + 1 x i f(x)=\sum^{n-1}_{i=0} a_ix^i, f'(x)=\sum^{n-2}_{i=0} (i+1)a_{i+1}x^i f(x)=i=0n1aixi,f(x)=i=0n2(i+1)ai+1xi
对数函数 ln ⁡ \ln ln的求导
f ( x ) = ln ⁡ ( x ) , f ′ ( x ) = 1 x f(x)=\ln(x), f'(x)=\frac{1}{x} f(x)=ln(x),f(x)=x1
复合函数求导——链式法则
f ( g ( x ) ) ′ = f ′ ( g ( x ) ) g ′ ( x ) f(g(x))'=f'(g(x))g'(x) f(g(x))=f(g(x))g(x)
求导的逆运算——积分

本题解法
g ( x ) ≡ ln ⁡ f ( x ) ( m o d    x n ) g(x)\equiv \ln f(x) (\mod x^n) g(x)lnf(x)(modxn)
两边同时求导可得
g ′ ( x ) ≡ f ′ ( x ) f ( x ) ( m o d    x n ) g'(x)\equiv \frac{f'(x)}{f(x)} (\mod x^n) g(x)f(x)f(x)(modxn)
结束!
多项式求逆算 1 f ( x ) \frac{1}{f(x)} f(x)1,再和 f ′ ( x ) f'(x) f(x)相乘即可得到 g ′ ( x ) g'(x) g(x)
(多项式求逆见蒟蒻一篇博客 https://blog.youkuaiyun.com/suncongbo/article/details/84485718)
g ′ ( x ) g'(x) g(x)积个分得到 g ( x ) g(x) g(x). 常数项,直接为 0 0 0.
时间复杂度 O ( n log ⁡ n ) O(n\log n) O(nlogn)
常数,我写的大概 9 9 9倍吧,求逆是 6 6 6倍,再做个乘法就是 3 3 3倍。
UPD: 仔细想了一下我这个常数好像是 18 18 18倍(见我多项式求逆那篇博客)

代码

#include<cstdio>
#include<cstdlib>
#include<cstring>
#include<algorithm>
#define llong long long
#define ldouble long double
#define uint unsigned int
#define ullong unsigned long long
#define udouble unsigned double
#define uldouble unsigned long double
#define modinc(x) {if(x>=P) x-=P;}
#define pii pair<int,int>
#define piii pair<pair<int,int>,int>
#define piiii pair<pair<int,int>,pair<int,int> >
#define pli pair<llong,int>
#define pll pair<llong,llong>
#define Memset(a,x) {memset(a,x,sizeof(a));}
using namespace std;

const int N = 1<<19;
const int P = 998244353;
const int LGN = 19;
const int G = 3;
llong a[N+3];
llong b[N+3];
llong tmp1[N+3],tmp2[N+3],tmp3[N+3],tmp4[N+3]; //inv
llong tmp7[N+3],tmp8[N+3],tmp9[N+3],tmp10[N+3]; //ln
int id[N+2];
int n;

void initid(int _len)
{
 id[0] = 0;
 for(int i=1; i<(1<<_len); i++) id[i] = (id[i>>1]>>1)|((i&1)<<(_len-1));
}

llong quickpow(llong x,llong y)
{
 llong cur = x,ret = 1ll;
 for(int i=0; y; i++)
 {
  if(y&(1ll<<i))
  {
   y-=(1ll<<i); ret = ret*cur%P;
  }
  cur = cur*cur%P;
 }
 return ret;
}
llong mulinv(llong x) {return quickpow(x,P-2);}

void ntt(int dgr,int coe,llong poly[],llong ret[])
{
 int len = 0; for(int i=0; i<=LGN; i++) if((1<<i)==dgr) {len = i; break;}
 initid(len); for(int i=0; i<dgr; i++) ret[i] = 0ll;
 for(int i=0; i<dgr; i++) ret[i] = poly[i];
 for(int i=0; i<dgr; i++) if(i<id[i]) swap(ret[i],ret[id[i]]);
 for(int i=1; i<=(dgr>>1); i<<=1)
 {
  llong tmp = quickpow(G,(P-1)/(i<<1));
  if(coe==-1) tmp = mulinv(tmp);
  for(int j=0; j<dgr; j+=(i<<1))
  {
   llong expn = 1ll;
   for(int k=0; k<i; k++)
   {
    llong x = ret[j+k],y = (expn*ret[j+i+k])%P;
    ret[j+k] = x+y; modinc(ret[j+k]);
    ret[j+i+k] = x-y+P; modinc(ret[j+i+k]);
    expn = (expn*tmp)%P;
   }
  }
 }
 if(coe==-1)
 {
  llong tmp = mulinv(dgr);
  for(int i=0; i<dgr; i++) ret[i] = ret[i]*tmp%P;
 }
}

void polyinv(int dgr,llong poly[],llong ret[])
{
 for(int i=0; i<dgr; i++) ret[i] = 0ll;
 ret[0] = mulinv(poly[0]);
 for(int i=1; i<=(dgr>>1); i<<=1)
 {
  for(int j=0; j<(i<<2); j++) tmp1[j] = j<i ? ret[j] : 0ll;
  for(int j=0; j<(i<<2); j++) tmp2[j] = j<(i<<1) ? poly[j] : 0ll;
  ntt((i<<2),1,tmp1,tmp3); ntt((i<<2),1,tmp2,tmp4);
  for(int j=0; j<(i<<2); j++) tmp3[j] = tmp3[j]*tmp3[j]%P*tmp4[j]%P;
  ntt((i<<2),-1,tmp3,tmp4);
  for(int j=0; j<(i<<1); j++) ret[j] = (tmp1[j]+tmp1[j]-tmp4[j]+P)%P;
 }
 for(int i=dgr; i<(dgr<<1); i++) ret[i] = 0ll;
}

void polyder(int dgr,llong poly[],llong ret[])
{
 for(int i=0; i<dgr-1; i++) ret[i] = poly[i+1]*(i+1)%P;
}

void polyint(int dgr,llong poly[],llong ret[])
{
 for(int i=1; i<=dgr; i++) ret[i] = poly[i-1]*mulinv(i)%P;
}

void polyln(int dgr,llong poly[],llong ret[])
{
 polyder(dgr,poly,tmp7);
 polyinv(dgr,poly,tmp8);
 ntt((dgr<<1),1,tmp8,tmp9); ntt((dgr<<1),1,tmp7,tmp10);
 for(int i=0; i<(dgr<<1); i++) tmp9[i] = tmp9[i]*tmp10[i]%P;
 ntt((dgr<<1),-1,tmp9,tmp10);
 polyint(dgr,tmp10,ret);
}

int main()
{
 scanf("%d",&n); int dgr = 1; while(dgr<=n) dgr<<=1;
 for(int i=0; i<n; i++) scanf("%lld",&a[i]);
 polyln(dgr,a,b);
 for(int i=0; i<n; i++) printf("%lld ",b[i]);
 return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值