拉格朗日插值与多项式乘法
进军多项式。
1. 拉格朗日插值
1.1. 普通插值
首先给出公式:
解释:对于每对点值
首先构造函数
通常情况下,题目会要求我们求出
1.2. 连续取值插值
很多情况下,我们求出的点值
记
预处理阶乘就可以线性插值,应用见例题 II。
1.3. 求 各项系数
构造函数
1.4. 例题
I. P4781 【模板】拉格朗日插值
板子题。
#include <bits/stdc++.h>
using namespace std;
#define ll long long
const ll mod=998244353;
const int N=2e3+5;
ll ksm(ll a,ll b){
ll s=1;
while(b){
if(b&1)s=s*a%mod;
a=a*a%mod,b>>=1;
} return s;
}
int n,k,x[N],y[N],ans;
int main(){
cin>>n>>k;
for(int i=1;i<=n;i++)cin>>x[i]>>y[i];
for(ll i=1,s1=1,s2=1;i<=n;i++,s1=s2=1){
for(int j=1;j<=n;j++)if(i!=j)s1=s1*(k-x[j])%mod,s2=s2*(x[i]-x[j])%mod;
ans=(ans+y[i]*s1%mod*ksm(s2,mod-2))%mod;
} cout<<(ans%mod+mod)%mod<<endl;
return 0;
}
II. CF622F The Sum of the k-th Powers
经典题。一个结论是自然数
时间复杂度
#include <bits/stdc++.h>
using namespace std;
#define ll long long
const ll mod=1e9+7;
const int N=1e6+5;
ll ksm(ll a,ll b){
ll s=1;
while(b){
if(b&1)s=s*a%mod;
a=a*a%mod,b>>=1;
} return s;
} ll inv(ll x){return ksm(x,mod-2);}
ll n,k,ans;
ll p[N],s[N],fc[N];
int main(){
cin>>n>>k,s[k+2]=1;
for(int i=0;i<=k+1;i++)fc[i]=i?fc[i-1]*i%mod:1,p[i]=i?p[i-1]*(n-i)%mod:n;
for(int i=k+1;~i;i--)s[i]=i==k+1?n-i:s[i+1]*(n-i)%mod;
for(ll i=1,res=0;i<=k+1;i++){
res=(res+ksm(i,k))%mod;
ans=(ans+p[i-1]*s[i+1]%mod*res%mod*ksm(fc[i]*((k-i)&1?1:-1)*fc[k+1-i]%mod,mod-2))%mod;
} cout<<(ans%mod+mod)%mod<<endl;
return 0;
}
III. P4463 [集训队互测 2012] calc
不妨设
首先把转移方程写出来:
-
接下来我们分析一下
的次数:首先我们有
是关于 的 次多项式。接下来使用一个小 Trick。Trick:差分分析次数。
根据转移方程
,有 。由于将两个相差 的数带入一个 次多项式得到的差是 次多项式,我们有 的次数 等于 的次数 ,因此 是关于 的 次多项式。所以拉格朗日插值即可。
const int N=1.5e3+5;
ll f[N][N],k,n,p,ans;
ll ksm(ll a,ll b){
ll s=1;
while(b){
if(b&1)s=s*a%p;
a=a*a%p,b>>=1;
}
return s;
}
int main(){
cin>>k>>n>>p,f[0][0]=1;
for(ll i=1;i<=n*3+4;i++)
for(ll j=0;j<=min(i,n);j++)
f[i][j]=(f[i-1][j]+(j?f[i-1][j-1]*i:0))%p;
for(ll i=n;i<=n*3+3;i++){
ll s1=f[i][n],s2=1;
for(ll j=n;j<=n*3+3;j++)if(i!=j)
s1=s1*(k+p-j)%p,
s2=s2*(i+p-j)%p;
ans=(ans+s1*ksm(s2,p-2))%p;
}
for(ll i=1;i<=n;i++)ans=ans*i%p;
cout<<ans<<endl;
return 0;
}
*IV. [BZOJ2137]submultiple
题意简述:对于
的所有约数 ,求 。 由唯一分解给出。 对于
的数据, , ;对于剩下 的数据, , 。
, 表示 由前 小的质数组成。
一个显然的结论是我们只关心唯一分解后每个质因子的次数
根据加法对乘法的分配律,稍作化简:
设
综上,时间复杂度为
template <class T> void cmin(T &a, T b){a = a < b ? a : b;}
template <class T> void cmax(T &a, T b){a = a > b ? a : b;}
bool Mbe;
const int N = 1e5 + 5;
const ll mod = 1e9 + 7;
ll ksm(ll a, ll b = mod - 2) {
ll s = 1;
while(b) {
if(b & 1) s = s * a % mod;
a = a * a % mod, b >>= 1;
} return s;
}
ll n, k, type = 1, pw[N];
void solve1() {
static ll f[N], ans = 1; f[0] = 0;
for(int i = 1; i < N; i++)
f[i] = (f[i - 1] + ksm(i, k)) % mod;
for(int i = 1; i <= n; i++)
ans = ans * f[pw[i] + 1] % mod;
cout << ans << endl;
}
void solve2() {
static ll f[N], ans = 1; f[0] = 0;
for(int i = 1; i <= k + 1; i++) f[i] = (f[i - 1] + ksm(i, k)) % mod;
for(int i = 1; i <= n; i++) pw[i] %= mod;
for(int i = 1; i <= n; i++) {
ll val = 0;
for(int j = 0; j <= k + 1; j++) {
ll nume = f[j], deno = 1;
for(int p = 0; p <= k + 1; p++) if(p != j)
deno = deno * (mod + j - p) % mod,
nume = nume * (pw[i] + 1 + mod - p) % mod;
val = (val + nume * ksm(deno)) % mod;
}
ans = ans * val % mod;
} cout << ans << endl;
}
bool Med;
int main(){
fprintf(stderr, "%.2lf\n", (&Mbe - &Med) / 1048576.0);
cin >> n >> k;
for(int i = 1; i <= n; i++) pw[i] = read(), type &= pw[i] <= 1e5;
if(type) solve1();
else solve2();
return 0;
}
*V. [BZOJ3453]tyvj 1858 XLkxc
题意简述:设
表示 , 表示 。给定 ,求 。
, 。
感觉数据范围像是出题人随手打的(大雾。
经典结论:
考虑只算一个
如果我们把后面那个
不慌,对
其中
代码偷懒使用了
template <class T> void cmin(T &a, T b){a = a < b ? a : b;}
template <class T> void cmax(T &a, T b){a = a > b ? a : b;}
bool Mbe;
const int N = 1e3 + 5;
const ll mod = 1234567891;
ll ksm(ll a, ll b = mod - 2) {
ll s = 1;
while(b) {
if(b & 1) s = s * a % mod;
a = a * a % mod, b >>= 1;
} return s;
}
ll k, a, n, d, c, y[N];
void solve() {
ll ans = 0;
cin >> k >> a >> n >> d, c = k + 3;
for(int i = 1; i <= c; i++) y[i] = (y[i - 1] + ksm(i, k)) % mod;
for(int i = 1; i <= c; i++) y[i] = (y[i - 1] + y[i]) % mod;
for(int i = 1; i <= c; i++) {
ll coef = 0;
static ll y2[N];
for(int j = 0; j <= c + 2; j++) {
y2[j] = j ? y2[j - 1] : 0;
ll deno = 1, nume = 1;
for(int p = 1; p <= c; p++) if(i != p)
nume = nume * ((a + j * d) % mod + mod - p) % mod,
deno = deno * (i + mod - p) % mod;
y2[j] = (y2[j] + nume * ksm(deno)) % mod;
} // 第一遍插值插出 h_i
for(int j = 1; j <= c + 2; j++) {
ll deno = 1, nume = y2[j];
for(int p = 1; p <= c + 2; p++) if(j != p)
nume = nume * (n + mod - p) % mod,
deno = deno * (j + mod - p) % mod;
coef = (coef + nume * ksm(deno)) % mod;
} // 第二遍插值插出 H_i
ans = (ans + coef * y[i]) % mod;
} cout << ans << endl;
}
bool Med;
signed main(){
fprintf(stderr, "%.2lf\n", (&Mbe - &Med) / 1048576.0);
int T; cin >> T;
while(T--) solve();
return 0;
}
2. 多项式乘法:加法卷积
2.1. FFT
对于一个
点值的取法很有讲究,高明的方法能够极大化地减小时间复杂度。这里我们采用
。 。 。 ,从而计算 次单位根。
当
递归处理很慢,于是我们使用迭代:考虑系数
卡常技巧:
- 结构体不要写构造函数。
- 重载加减乘运算符放到
struct
里面。
2.2. NTT
由于复数运算很慢,而通常情况下我们是在模意义下进行多项式运算,所以当模数取一些特殊值时,我们可以用
鸽着。
2.3. FFT 优化字符串匹配
Trick 1:翻转一个序列常常可以使关于它的某些计算变成卷积的形式。
对于一个文本串
对于有通配符的字符串,我们不妨将该位置上的值设为
Trick 2:一般情况下,上述方法足够应付多数题目。但是如果万恶的出题人卡了 FFT 精度就凉凉了。因此,为了保险,应尽量使用 NTT。但是 NTT 也有一个致命问题:如果计算出来的值刚好是
也许看了上述分析的你认为:既然将
例题 & 代码可以看 II.
2.4. 任意模数:MTT
MTT 又称为任意模数 FFT。
2.4.1. 拆系数:7 次 FFT
对于值域为
对此,我们可以将系数拆分成
综上,我们有了一个显然的 4 DFT + 3 IDFT = 7 FFT 的做法,但是不够快。
2.4.2. 优化 Trick:合并 DFT 两个实系数多项式
构造多项式
如果我们先对
同时 DFT 两个实系数多项式,可以压缩成一次 DFT。大大减小了常数。
2.4.3. 4 次 FFT
根据 2.4.2. 的 Trick,原来的 4 DFT 就被优化成了 2 DFT。四维变二维。
有了
我们借鉴上述思想,构造多项式
对于
综上,我们只需要进行 2 DFT + 2 IDFT = 4 FFT,常数非常优秀。代码见例题 III.
2.5. 任意模数:三模数 NTT
考虑对
一般选模数分别为
不过常数稍大,可以手写结构体将对于这三个模数分别取模后的值打包,这样常数会小一些。
2.6. 任意长度:Bluestein 算法
2.7. 例题
I. P3803 【模板】多项式乘法(FFT)
FFT:
#include <bits/stdc++.h>
using namespace std;
#define ld double
const int N=1<<21;
const ld Pi=acos(-1);
struct com{
ld x,y;
com operator + (com b){return (com){x+b.x,y+b.y};}
com operator - (com b){return (com){x-b.x,y-b.y};}
com operator * (com b){return (com){x*b.x-y*b.y,x*b.y+y*b.x};}
}a[N],b[N];
int n,m,lim=1,bit,r[N];
void FFT(com *a,int tp){
for(int i=0;i<lim;i++)if(i<r[i])swap(a[i],a[r[i]]);
for(int l=1;l<lim;l<<=1){
com wn={cos(Pi/l),tp*sin(Pi/l)};
for(int j=0;j<lim;j+=(l<<1)){
com w={1,0},x,y;
for(int k=0;k<l;k++,w=w*wn)
x=a[j+k],y=w*a[j+k+l],a[j+k]=x+y,a[j+k+l]=x-y;
}
}
}
int main(){
cin>>n>>m;
for(int i=0;i<=n;i++)scanf("%lf",&a[i].x);
for(int i=0;i<=m;i++)scanf("%lf",&b[i].x);
while(lim<=n+m)lim<<=1,bit++;
for(int i=1;i<lim;i++)r[i]=(r[i>>1]>>1)|((i&1)<<bit-1);
FFT(a,1),FFT(b,1);
for(int i=0;i<lim;i++)a[i]=a[i]*b[i];
FFT(a,-1);
for(int i=0;i<=n+m;i++)cout<<(int)(a[i].x/lim+0.5)<<" ";
return 0;
}
NTT:
#include <bits/stdc++.h>
using namespace std;
#define ll long long
#define ull unsigned long long
#define gc getchar()
inline int read(){
int x=0; char s=gc;
while(!isdigit(s))s=gc;
while(isdigit(s))x=s-'0',s=gc;
return x;
}
const ll mod=998244353;
const int N=1<<21;
ll ksm(ll a,ll b){
ll s=1;
while(b){
if(b&1)s=s*a%mod;
a=a*a%mod,b>>=1;
} return s;
} ll inv(ll x){return ksm(x,mod-2);}
const int G=3;
const int ivG=inv(3);
ll tr[N],lim=1,l;
ll n,m,f[N],g[N];
void NTT(ll *a,bool tp){
static ull f[N],w[N]; w[0]=1;
for(int i=0;i<lim;i++)f[i]=a[tr[i]];
for(int l=1;l<lim;l<<=1){
ll wn=ksm(tp?G:ivG,(mod-1)/(l+l));
for(int i=1;i<l;i++)w[i]=w[i-1]*wn%mod;
for(int i=0;i<lim;i+=l<<1){
for(int j=0;j<l;j++){
int y=w[j]*f[i|j|l]%mod;
f[i|j|l]=f[i|j]+mod-y,f[i|j]+=y;
}
} if(l==(1<<17))for(int i=0;i<lim;i++)f[i]%=mod;
}
if(!tp){
ll iv=inv(lim);
for(int i=0;i<lim;i++)a[i]=f[i]%mod*iv%mod;
} else for(int i=0;i<lim;i++)a[i]=f[i]%mod;
}
int main(){
cin>>n>>m;
for(int i=0;i<=n;i++)f[i]=read();
for(int i=0;i<=m;i++)g[i]=read();
while(lim<=n+m)lim<<=1,l++;
for(int i=1;i<lim;i++)tr[i]=(tr[i>>1]>>1)|((i&1)<<l-1);
NTT(f,1),NTT(g,1);
for(int i=0;i<lim;i++)f[i]=f[i]*g[i]%mod;
NTT(f,0);
for(int i=0;i<=n+m;i++)printf("%lld ",f[i]);
}
II. P4173 残缺的字符串
经典字符串匹配题。
#include <bits/stdc++.h>
using namespace std;
typedef double db;
typedef long long ll;
typedef long double ld;
typedef unsigned long long ull;
#define gc getchar()
#define pb push_back
#define mem(x,v,n) memset(x,v,sizeof(int)*n)
#define cpy(x,y,n) memcpy(x,y,sizeof(int)*n)
const ld Pi=acos(-1);
const ll mod=998244353;
inline int read(){
int x=0; char s=gc;
while(!isdigit(s))s=gc;
while(isdigit(s))x=x*10+s-'0',s=gc;
return x;
}
ll ksm(ll a,ll b){
ll s=1;
while(b){
if(b&1)s=s*a%mod;
a=a*a%mod,b>>=1;
}
return s;
}
ll inv(ll x){return ksm(x,mod-2);}
const int N=1<<19;
const ll G=3;
const ll ivG=inv(3);
int r[N],pren;
void pre(int n){
if(n==pren)return;
for(int i=1;i<n;i++)r[i]=(r[i>>1]>>1)|(i&1?n>>1:0);
}
void NTT(int *g,int n,bool op){
pre(n);
static ull f[N],w[N]; w[0]=1;
for(int i=0;i<n;i++)f[i]=g[r[i]];
for(int l=1;l<n;l<<=1){
ull wn=ksm(op?G:ivG,(mod-1)/(l+l));
for(int i=1;i<l;i++)w[i]=w[i-1]*wn%mod;
for(int i=0;i<n;i+=l<<1)
for(int j=0;j<l;j++){
int t=w[j]*f[i|j|l]%mod;
f[i|j|l]=f[i|j]+mod-t,f[i|j]+=t;
}
if(l==(1<<16))for(int i=0;i<n;i++)f[i]%=mod;
}
if(op)for(int i=0;i<n;i++)g[i]=f[i]%mod;
else{
ll iv=inv(n);
for(int i=0;i<n;i++)g[i]=f[i]%mod*iv%mod;
}
}
int n,m,lim=1,a[N],b[N],res[N];
int ans[N],cnt;
string A,B;
int main(){
cin>>n>>m>>A>>B,reverse(A.begin(),A.end());
while(lim<m)lim<<=1;
for(int i=0;i<n;i++)if(A[i]!='*')a[i]=(A[i]-'a')*(A[i]-'a');
for(int i=0;i<m;i++)if(B[i]!='*')b[i]=1;
NTT(a,lim,1),NTT(b,lim,1);
for(int i=0;i<lim;i++)res[i]=1ll*a[i]*b[i]%mod;
mem(a,0,lim),mem(b,0,lim);
for(int i=0;i<n;i++)if(A[i]!='*')a[i]=A[i]-'a';
for(int i=0;i<m;i++)if(B[i]!='*')b[i]=B[i]-'a';
NTT(a,lim,1),NTT(b,lim,1);
for(int i=0;i<lim;i++)res[i]=(res[i]-2ll*a[i]*b[i]%mod+mod)%mod;
mem(a,0,lim),mem(b,0,lim);
for(int i=0;i<n;i++)if(A[i]!='*')a[i]=1;
for(int i=0;i<m;i++)if(B[i]!='*')b[i]=(B[i]-'a')*(B[i]-'a');
NTT(a,lim,1),NTT(b,lim,1);
for(int i=0;i<lim;i++)res[i]=(res[i]+1ll*a[i]*b[i])%mod;
NTT(res,lim,0);
for(int i=n-1;i<m;i++)if(!res[i])ans[++cnt]=i-n+2;
cout<<cnt<<endl;
for(int i=1;i<=cnt;i++)cout<<ans[i]<<" ";
return 0;
}
III. P4245 【模板】任意模数多项式乘法
const int N=(1<<18)+1;
const int B=32767;
struct cp{
double re,im;
cp operator + (cp x){return (cp){re+x.re,im+x.im};}
cp operator - (cp x){return (cp){re-x.re,im-x.im};}
cp operator * (cp x){return (cp){re*x.re-im*x.im,re*x.im+im*x.re};}
cp operator * (double x){return (cp){re*x,im*x};}
cp operator / (double x){return (cp){re/x,im/x};}
cp conj (){return (cp){re,-im};}
}I,w[N],a0[N],b0[N],a1[N],b1[N],P[N],Q[N];
int lim=1,r[N];
void init(){
for(int i=1;i<lim;i++)r[i]=(r[i>>1]>>1)|(i&1?lim>>1:0);
for(int i=0;i<=lim;i++)w[i]=(cp){cos(2*i*Pi/lim),sin(2*i*Pi/lim)};
}
void FFT(cp *a,int op){
static cp f[N];
for(int i=0;i<lim;i++)f[i]=a[r[i]];
for(int l=1;l<lim;l<<=1)
for(int i=0,b=lim/l>>1;i<lim;i+=l<<1)
for(int j=0,c=0;j<l;j++,c+=b){
cp k=f[i|j|l]*w[~op?c:lim-c];
f[i|j|l]=f[i|j]-k,f[i|j]=f[i|j]+k;
}
for(int i=0;i<lim;i++)a[i]=op==1?f[i]:f[i]/lim;
}
void FFFT(cp *a,cp *b){
for(int i=0;i<lim;i++)a[i].im=b[i].re; FFT(a,1);
for(int i=0;i<lim;i++)b[i]=a[i?lim-i:0].conj();
for(int i=0;i<lim;i++){
cp p=a[i],q=b[i];
a[i]=(p+q)*0.5,b[i]=(q-p)*0.5*I;
}
}
int n,m,p;
int main(){
cin>>n>>m>>p,I={0,1};
for(int i=0,v;i<=n;i++)v=read()%p,a0[i].re=v>>15,a1[i].re=v&B;
for(int i=0,v;i<=m;i++)v=read()%p,b0[i].re=v>>15,b1[i].re=v&B;
while(lim<=n+m)lim<<=1; init();
FFFT(a0,a1),FFFT(b0,b1);
for(int i=0;i<lim;i++){
P[i]=a0[i]*b0[i]+a0[i]*b1[i]*I,
Q[i]=a1[i]*b0[i]+a1[i]*b1[i]*I;
}
FFT(P,-1),FFT(Q,-1);
for(int i=0;i<=n+m;i++){
ll p1=(ll)(P[i].re+0.5)%p+p;
ll p2=(ll)(P[i].im+Q[i].re+0.5)%p+p;
ll p3=(ll)(Q[i].im+0.5)%p+p;
printf("%lld ",((p1<<30)+(p2<<15)+p3)%p);
}
return 0;
}
IV. P3723 [AH2017/HNOI2017]礼物
设第
const ll G=3;
const ll ivG=inv(3);
const int N=1<<18;
int n,m,len,r[N];
void init(int n){for(int i=1;i<n;i++)r[i]=(r[i>>1]>>1)|(i&1?n>>1:0);}
void NTT(ll *a,int n,bool op){
static ull f[N],w[N]; w[0]=1;
for(int i=0;i<n;i++)f[i]=a[r[i]];
for(int l=1;l<n;l<<=1){
ll wn=ksm(op?G:ivG,(mod-1)/(l+l));
for(int i=1;i<l;i++)w[i]=w[i-1]*wn%mod;
for(int i=0;i<n;i+=l+l)
for(int j=0;j<l;j++){
int y=f[i|j|l]*w[j]%mod;
f[i|j|l]=f[i|j]+mod-y,f[i|j]+=y;
} if(l==(1<<16))for(int i=0;i<n;i++)f[i]%=mod;
} if(!op){
ll iv=inv(n);
for(int i=0;i<n;i++)a[i]=f[i]%mod*iv%mod;
} else for(int i=0;i<n;i++)a[i]=f[i]%mod;
}
ll a[N],b[N];
ll ssq,ss,res,ans=1e10;
int main(){
cin>>n>>m;
for(int i=0;i<n;i++)scanf("%d",&a[i]),ssq+=a[i]*a[i],ss+=a[i];
for(int i=0;i<n;i++)scanf("%d",&b[i]),ssq+=b[i]*b[i],ss-=b[i],b[i+n]=b[i];
reverse(a,a+n);
len=1; while(len<3*n)len<<=1;
init(len),NTT(a,len,1),NTT(b,len,1);
for(int i=0;i<len;i++)a[i]=a[i]*b[i]%mod;
NTT(a,len,0);
for(int i=n-1;i<2*n;i++)res=max(res,a[i]);
for(int i=-m;i<=m;i++)ans=min(ans,ssq+2*ss*i+n*i*i-2*res);
cout<<ans<<endl;
return 0;
}
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· go语言实现终端里的倒计时
· 如何编写易于单元测试的代码
· 10年+ .NET Coder 心语,封装的思维:从隐藏、稳定开始理解其本质意义
· .NET Core 中如何实现缓存的预热?
· 从 HTTP 原因短语缺失研究 HTTP/2 和 HTTP/3 的设计差异
· 分享一个免费、快速、无限量使用的满血 DeepSeek R1 模型,支持深度思考和联网搜索!
· 使用C#创建一个MCP客户端
· ollama系列1:轻松3步本地部署deepseek,普通电脑可用
· 基于 Docker 搭建 FRP 内网穿透开源项目(很简单哒)
· 按钮权限的设计及实现