拉格朗日插值法&乘数法学习笔记
拉格朗日乘数法
这玩意有什么用
拉格朗日乘数法是用于解决一类多变量的有限制最值。
具体而言,就是给定一条带有n个变量的等式,如$f(x_1,...,x_n)=0$作为限制
求一个包含n个变量的式子的最值,如$g(x_1,...,x_n)$
这玩意怎么搞
我们把f和g的图像在坐标系中画出来
通过一堆复杂的证明(感性理解也行)可知当这两个图像相切(这里拿n=2举例)时,切点坐标对应的$(x_1,..,x_n)$就是最值取得的地方
显然,相切处两图像的切线斜率一致(严谨的话应该叫坡度向量),即该处的导数一致。这样我们就可以求出两函数的导函数,然后列方程求解即可
注意f,g是多元函数,所以列方程时应该把每一元的偏导数(即把这一元当作主元,其他元当作常量)单独拿出来列方程,这样我们就有n+1条方程(包括最开始的约束方程),求解即可。
求解方程的方法试题目而定,如noi川藏旅行中结合单调性求解,或牛顿迭代法解决。
例题
noi骑行川藏
#include<iostream> #include<cstdio> using namespace std; #define N 10010 double v2[N],e,v[N],s[N],k[N],v3[N]; int n; bool check(double mid) { double t=0; for(int i=1;i<=n;i++) { double l=v3[i],r=100000; while(r-l>=1e-15*5) { double x=(l+r)/2.0 ; if(mid*2*k[i]*x*x*(x-v[i])+1>=0) l=x; else r=x; } v2[i]=l; if(v2[i]<=v[i]) return false; t+=k[i]*s[i]*(v2[i]-v[i])*(v2[i]-v[i]); } if(t>e) return false; for(int i=1;i<=n;i++) v3[i]=v2[i]; return true; } int main() { cin>>n>>e; for(int i=1;i<=n;i++) scanf("%lf%lf%lf",&s[i],&k[i],&v[i]); double l=-100000,r=0; while(r-l>1e-15*5) { double mid=(l+r)/2.0; if(check(mid)) l=mid; else r=mid; } double ans=0; for(int i=1;i<=n;i++) ans+=s[i]/v3[i]; printf("%.8f",ans); }
拉格朗日插值法
用途
求过n个点的函数解析式(即解一元非线性方程组)
在OI题中一般用于求一些系数已知的递推式或求和式等的任意一项(可以很大)
比如什么自然数幂求和。
但使用前需知道最高次数(可以不精确,稍微大一点)
另外,有些范围很大的二维dp,如果转移方程固定,形如$f(i)(j)=c*f(k)(j-1)*(a*j+b)$且j较小,要求f(n)(m)。可以把j看作主元x,那么转移时j每加1,x的最高次就加1,所以f(n)(m)就是一个关于x的m次多项式,先求出前面的f(1)(m)到f(m)(m),然后插值解决f(n)(m)。(比如【BZOJ2655】calc)
如果给定点是连续的自然数,时间复杂度为$O(n)$(n为最高次)
否则为$O(n^2)$
做法
利用已知的系数将数值1,2,3,...n(多项式的项数)带入计算得到$y_1,y_2,...,y_n$
通过一些数学推导可得其解析式为$f(x)=\sum_{i=1}^n y_i*\frac{\prod_{j=1,j!=i}^n x-j}{\prod_{j=1,j!=i}^n i-j}$
那么,在求解数值x对应的函数值时,直接将x带入求解即可。
分子可以通过前后缀和进行优化,分母预处理阶乘即可