拉格朗日插值法

  • 拉格朗日插值法

         \ \ \ \ \ \ \,       拉格朗日插值法,是解决多项式点值表达式函数值的问题的算法,具体而言,问题如下:

         \ \ \ \ \ \ \,       已知在二维平面上,一多项式 f f f的函数图像经过 n + 1 n+1 n+1 个点 ( x i , y i ) (x_i,y_i) (xi,yi) ,既我们知道 f ( x i ) = y i f(x_i)=y_i f(xi)=yi,求 f ( k ) f(k) f(k)的值。

  • 初步分析

         \ \ \ \ \ \ \,       显然,经过 n + 1 n+1 n+1 个点的多项式函数一定是 n n n次函数,那么也很显然的,我们可以列出一个 n n n 元方程组来解决这个问题,形如:

{ y 1 = a 1 ⋅ x 1 n + a 2 ⋅ x 1 n − 1 + a 3 ⋅ x 1 n − 2 + . . . + a n + 1 y 2 = a 1 ⋅ x 2 n + a 2 ⋅ x 2 n − 1 + a 3 ⋅ x 2 n − 2 + . . . + a n + 1               . . . y n = a 1 ⋅ x n n + a 2 ⋅ x n n − 1 + a 3 ⋅ x n n − 2 + . . . + a n + 1 \begin{cases}y_1=a_1\cdot x_1^n+a_2\cdot x_1^{n-1}+a_3\cdot x_1^{n-2}+...+a_{n+1}\\y_2=a_1\cdot x_2^n+a_2\cdot x_2^{n-1}+a_3\cdot x_2^{n-2}+...+a_{n+1}\\\ \ \ \ \ \ \ \ \ \ \ \ \ ...\\y_n=a_1\cdot x_n^n+a_2\cdot x_n^{n-1}+a_3\cdot x_n^{n-2}+...+a_{n+1}\end{cases} y1=a1x1n+a2x1n1+a3x1n2+...+an+1y2=a1x2n+a2x2n1+a3x2n2+...+an+1             ...yn=a1xnn+a2xnn1+a3xnn2+...+an+1

         \ \ \ \ \ \ \,       直观的,我们可以高斯消元来做,复杂度 O ( n 3 ) O(n^3) O(n3),复杂度多半已上天,那么我们如何快速处理呢?

  • 插值法

         \ \ \ \ \ \ \,       我们现在知道的是 f ( x i ) = y i f(x_i)=y_i f(xi)=yi,那么我们想怎么快速表达出这个多项式,我们设定一种函数 S i S_i Si

S i ( x ) = [ x = x i ] S_i(x)=[x=x_i] Si(x)=[x=xi]

         \ \ \ \ \ \ \,       它的意义是只有当 x x x x i x_i xi 函数值才为 1 1 1,其他为 0 0 0,那么显然有:

f = ∑ i = 1 n y i ⋅ S i f=\sum_{i=1}^n y_i\cdot S_i f=i=1nyiSi

         \ \ \ \ \ \ \,       很显然的, S i S_i Si 是个 n n n 次多项式,而我们的答案也就是:

f ( k ) = ∑ i = 1 n y i ⋅ S i ( k ) f(k)=\sum_{i=1}^n y_i\cdot S_i(k) f(k)=i=1nyiSi(k)

         \ \ \ \ \ \ \,       遗憾地告诉你,光靠这个办法是不能还原 f f f的函数图像的,只能得到近似图像,所以插值法是有一定误差的。

         \ \ \ \ \ \ \,       现在我们简化了问题,如何求 S i S_i Si 是个 n n n 次多项式。

         \ \ \ \ \ \ \,       其实满足 S i ( x ) S_i(x) Si(x)的函数取值合法挺简单的,主要问题是如何满足他是 n n n 次多项式的事实,那么我们就先把它化作下面的形态:

S i ( k ) = ∏ j = 1 , j ≠ s o m e t h i n g n + 1 a j k + b j S_i(k)=\prod_{j=1,j\neq \rm something}^{n+1}a_jk+b_j Si(k)=j=1,j̸=somethingn+1ajk+bj

         \ \ \ \ \ \ \,       如何就卡住了,我们不知道怎么办,但是还记得吗,我们只能得到近似图像,所以我们只需要满足 S i ( x ) S_i(x) Si(x)的图像大约在 [ x = x i ] [x=x_i] [x=xi]就行了,拉格朗日给了一种解法:

S i ( k ) = ∏ j = 1 , j ≠ i n + 1 k − x j x i − x j S_i(k)=\prod_{j=1,j\neq i}^{n+1}\frac{k-x_j}{x_i-x_j} Si(k)=j=1,j̸=in+1xixjkxj

          \ \ \ \ \ \ \ \,        具体证明可以看这里:【 VictoryCzt_拉格朗日插值法学习笔记】

          \ \ \ \ \ \ \ \,        所以得到拉格朗日公式( n n n个点),复杂度是 O ( n 2 ) O(n^2) O(n2)的:

f ( k ) = ∑ i = 1 n y i ∏ j = 1 , j ≠ i n k − x j x i − x j f(k)=\sum_{i=1}^{n} y_i\prod_{j=1,j\neq i}^{n}\frac{k-x_j}{x_i-x_j} f(k)=i=1nyij=1,j̸=inxixjkxj

  • 代码实现:

         \ \ \ \ \ \ \,       普通的拉格朗日差值法其实代码很简单了:

double Lagrange(double *x,double *y,double n,double k){
	double top,bot,ret=0;
	for(double i=1;i<=n;i++){
		top=1.0;bot=1.0;
		for(double j=1;j<=n;++j)if(i!=j)
			top*=k-x[j],
			bot*=x[i]-x[j];
		ret+=y[i]*top/bot;
	}
	return ret;
}

       &ThinSpace; \ \ \ \ \ \ \,       有些时候呢,我们的 x i x_i xi 是连续的,所以说公式变形为:

f ( k ) = ∑ i = 1 n y i ∏ j = 1 , j ≠ i n k − j i − j f(k)=\sum_{i=1}^{n} y_i\prod_{j=1,j\neq i}^{n}\frac{k-j}{i-j} f(k)=i=1nyij=1,j̸=inijkj

       &ThinSpace; \ \ \ \ \ \ \,       我们令:

p r e i = ∏ j = 1 i ( k − j ) = p r e i − 1 × ( k − j ) pre_i=\prod_{j=1}^{i}(k-j)=pre_{i-1}\times(k-j) prei=j=1i(kj)=prei1×(kj)

s u f i = ∏ j = i n ( k − j ) = s u f i + 1 × ( k − j ) suf_i=\prod_{j=i}^{n}(k-j)=suf_{i+1}\times(k-j) sufi=j=in(kj)=sufi+1×(kj)

       &ThinSpace; \ \ \ \ \ \ \,       显然对于这两个函数是可以 O ( n ) O(n) O(n)预处理的。

       &ThinSpace; \ \ \ \ \ \ \,       原公式可以化成:

f ( k ) = ∑ i = 1 n y i p r e i − 1 × s u f i + 1 ( i − 1 ) ! × ( n − i ) ! f(k)=\sum_{i=1}^{n} y_i \frac{pre_{i-1}\times suf_{i+1}}{(i-1)!\times(n-i)!} f(k)=i=1nyi(i1)!×(ni)!prei1×sufi+1

       &ThinSpace; \ \ \ \ \ \ \,       注意分母是有符号需要判断的, n − i n-i ni为奇数时,分母为负。

       &ThinSpace; \ \ \ \ \ \ \,       显然阶乘也可以预处理,于是乎复杂度变成了 O ( n ) O(n) O(n)

long long pre[N],suf[N],fac[N];
long long Lagrange(long long *y,long long n,long long k){
	long long top,bot,ret=0;
	pre[0]=suf[n+1]=fac[0]=1;
	for(int i=1;i<=n;i++)pre[i]=pre[i-1]*(k-i);
	for(int i=n;i>=1;i--)suf[i]=suf[i+1]*(k-i);
	for(int i=2;i<=n;i++)fac=fac*i;
	for(int i=1;i<=n;i++){
		top*=pre[i-1]*suf[i+1],
		bot*=fac[i-1]*fac[n-i];
		if((n-i)&1) ret-=y[i]*top/bot;
		else ret+=y[i]*top/bot;
	}
	return ret;
}

       &ThinSpace; \ \ \ \ \ \ \,       题目大意:

       &ThinSpace; \ \ \ \ \ \ \,       给你一棵 n n n 个节点的树,以 1 1 1 为根节点,现在让你给每个节点分配一个权值 ∈ [ 1 , D ] ∈[1,D] [1,D],使得每个节点的权值不超过他的父亲节点( 1 1 1 号节点除外),问一共有多少种分配方式。 ( 1 ≤ n ≤ 3000 , 1 ≤ D ≤ 1 0 9 ) (1≤n≤3000, 1≤D≤10^9) (1n3000,1D109)

       &ThinSpace; \ \ \ \ \ \ \,       显然直观的有一个二维树形dp的做法,一维表示当前节点,一维表示这个节点的取值情况,来描述方案数:

f i , j = ∏ s ∈ S o n i f s , j + f i , j − 1 f_{i,j}=\prod_{s\in Son_i}f_{s,j}+f_{i,j-1} fi,j=sSonifs,j+fi,j1

       &ThinSpace; \ \ \ \ \ \ \,       答案就是 f 1 , D f_{1,D} f1,D了。

       &ThinSpace; \ \ \ \ \ \ \,       复杂度为 O ( n D ) O(nD) O(nD) 的,过不了,但是我们观察dp式子,可以发现……这个家伙是个多项式吧?继续大胆猜测,这个是个 n n n 维多项式。

       &ThinSpace; \ \ \ \ \ \ \,       所以我们用dp来预处理出 f 1 , i   ,   i ∈ [ 1 , n + 1 ] f_{1,i}\ ,\ {i\in[1,n+1]} f1,i , i[1,n+1],然后使用拉格朗日差值法,便可以求出 f 1 , D f_{1,D} f1,D 了,复杂度是 O ( n 2 ) O(n^2) O(n2) 的。

       &ThinSpace; \ \ \ \ \ \ \,       代码如下,取模真的取死人啊

#include<algorithm>
#include<iostream>
#include<cstdlib>
#include<cstring>
#include<cctype>
#include<cstdio>
#include<vector>
#include<string>
#include<queue>
#include<stack>
#include<cmath>
#include<map>
#include<set>
using namespace std;
const int inf=0x7fffffff;
const double eps=1e-10;
const double pi=acos(-1.0);
//char buf[1<<15],*S=buf,*T=buf;
//char getch(){return S==T&&(T=(S=buf)+fread(buf,1,1<<15,stdin),S==T)?0:*S++;}
inline int read(){
	int x=0,f=1;char ch;ch=getchar();
	while(ch<'0'||ch>'9'){if(ch=='-') f=0;ch=getchar();}
	while(ch>='0'&&ch<='9'){x=(x<<1)+(x<<3)+(ch&15);ch=getchar();}
	if(f)return x;else return -x;
}
const int N=3030;
const long long mod=1e9+7;
long long power(long long a,long long b){
	long long ans=1ll;
	for(;b;b>>=1,a=a*a%mod)if(b&1ll)ans=ans*a%mod;
	return ans;
}
int n;
long long D;
vector<int> G[N];
long long f[N][N];
void dfs(int u){
	for(int i=1;i<=n+1;i++)f[u][i]=1;
	for(int i=0;i<G[u].size();i++){
		dfs(G[u][i]);
		for(int j=1;j<=n+1;j++)
		f[u][j]=(f[u][j]*f[G[u][i]][j])%mod;
	}
	for(int x=1;x<=n+1;x++)f[u][x]=(f[u][x]+f[u][x-1])%mod;
}
long long pre[N],suf[N],fac[N];
long long Lagrange(long long *y,long long n,long long k){
  long long top,bot,ret=0,Fac;
  pre[0]=suf[n+1]=Fac=1ll;
	for(long long i=1;i<=n;i++)pre[i]=(pre[i-1]*(k-i+mod)%mod)%mod;
	for(long long i=n;i>=1;i--)suf[i]=(suf[i+1]*(k-i+mod)%mod)%mod;
  for(long long i=2;i<=n;i++)Fac=Fac*i%mod;
  fac[n]=power(Fac,mod-2);
  for(int i=n-1;i>=0;i--)fac[i]=fac[i+1]*1ll*(i+1)%mod;
  for(int i=1;i<=n;i++){
    top=(pre[i-1]*suf[i+1]%mod)%mod,
    bot=(fac[i-1]*fac[n-i]%mod)%mod;
    if((n-i)&1)ret=(ret-(y[i]*top%mod*bot%mod)+mod)%mod;
    else ret=(ret+(y[i]*top%mod*bot%mod))%mod;
  }
  return (ret+mod)%mod;
}
int main()
{
	n=read();scanf("%I64d",&D);D%=mod;
	for(int i=2,a;i<=n;i++)
	a=read(),G[a].push_back(i);
	dfs(1);
	if(D<=1ll*n+1ll)printf("%I64d\n",f[1][D]);
	else printf("%I64d\n",Lagrange(f[1],n+1,D));
	return 0;
}

  • 2
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值