浅谈牛顿迭代法的应用、拓展及模板
sjh0626
·
·
算法·理论
简介
牛顿迭代法可用来解非线性方程 f(x)=0 的所有根(包括复数解)。
公式
我们目前已知 f(x) 在 x_n 处的一阶泰勒展开:
f(x)\approx f(x_n)+f^{'}(x_n)(x-x_n)
然后再解一下 x:
\begin{aligned}
f(x) &\approx f(x_n)+f^{'}(x_n)x-f^{'}(x_n)x_n \\
f(x) - f(x_n) + f^{'}(x_n)x_n &\approx f^{'}(x_n)x \\
\frac{f(x)-f(x_n)+f^{'}(x_n)x_n}{f^{'}(x_n)} &\approx x\\
x &\approx \frac{f(x)-f(x_n)+f^{'}(x_n)x_n}{f^{'}(x_n)}\\
x &\approx \frac{f(x)-f(x_n)}{f^{'}(x_n)}+x_n
\end{aligned}
因为已知 f(x)=0,并且我们为了迭代,还是把约等于改成等于吧,所以最后公式为:
x_{n+1} = x_n-\frac{f(x_n)}{f^{'}(x_n)}
怎么样?是不是一下子就豁然开朗了?
例题
求平方根
是的,你没听错,一个 sqrt 就能够搞定的事,居然还要手搓牛迭?
但是,这是每个初学者都要经历的,所以,继续往下看吧!
题目:给定一个非线性方程 x^2=n,求 x 的值。
有的人就要说了:这也不是等于 0 啊,你不会在骗人吧?
别急,我们观察方程,注意到可以构造为 f(x)=x^2-n=0 的方程。
所以可以做了,我们先求出 f(x) 的导数,即 f^{'}(x)=2x。
可以直接上公式了!
double f(double x){return x*x-n;}
double df(double x){return 2*x;}
double solve(){
double x=1;
while(fabs(f(x))>1e-6){x=x-f(x)/df(x);}
}
我们随便带入一个 n 吧,比如 2,可得 \sqrt{2} \approx1.41421356。
求多项式多解
题目 1:UVA10428 The Roots
这时有人发出了尖锐的爆鸣声:“???求出所有根,不是,这是人啊?”
别急,我们就需要用到一个非常好的东西:因式定理。
我们该如何证明呢?
我们假设一个多项式方程 f(x)=a_nx^n+a_{n-1}x^n+\dots+a_0=0。
那么这个多项式一定可以分解为 f(x)=(x-x_n)\times(x-x_{n-1})\times\dots\times(x-x_1)。
因为这个方程是一元 n 次方程,那么也一定有 n 个根(含复根、重根),那么 n 个根 x_1,x_2,\dots,x_n 就可以对应 (x-x_1)=0,(x-x_2)=0,\dots,(x-x_n)=0。
如果还不懂,就可以理解为当 x=x_1,x_2,\dots,x_n 时 f(x) 一定等于 0。
那么事情就变得简单了,我们可以先牛顿迭代出其中的一个根 z,然后就可以用 f(x) 去除以 (x-z),然后继续牛顿迭代,循环往复,一定能求出 n 个根。
至于多项式除法:
随便带入一个多项式进去吧。
设 ax^3+bx^2+cx+d=(x-z)(nx^2+mx+p)=nx^3+(m-zn)x^2+(p-zm)x-zp。
可得 n=a,m=b+zn,p=c+zm。
再将 n,m,p,a,b,c,d 分别设为 b_2,b_1,b_0,a_3,a_2,a_1,a_0。
我们可得 b_i=a_{i+1}+b_{i+1}z,这就是新多项式的每一项系数了。
诶,这时就会有人问了:“f(x) 的导数咋求啊?”
这简单,我们可知幂函数 x^n 的导数为 nx^{n-1},并且导数有一个法则:F(x)=f(x)+g(x) 可求出导数为 F^{'}(x)=f^{'}(x)+g^{'}(x)。于是每一项就正常处理就对了。
代码:
#include<bits/stdc++.h>
#define sjh0626s return
#define code 0
using namespace std;
int n,cnt=0,len;
double a[100010],temp,nx,z[100010];
double f(double x){
double res=0;
for(int i=n;i>=0;i--){
res+=a[i]*pow(x,i);
}
return res;
}
double d(double x){
double res=0;
for(int i=n;i>=1;i--){
res+=a[i]*i*pow(x,i-1);
}
return res;
}
double newton_root(double x){
for(int i=1;i<=150;i++){
x-=f(x)/d(x);
}
return x;
}
void next_equation(double x){
double b[100010]={0};
for(int i=n-1;i>=0;i--)b[i]=a[i+1]+b[i+1]*x;
for(int i=n;i>=0;i--)a[i]=b[i];
}
int main(){
cin>>n;
while(n!=0){
for(int i=n;i>=0;i--){
cin>>a[i];
}
cout<<"Equation "<<++cnt<<": ";
len=0;
for(n;n>=1;n--){
nx=newton_root(1);
z[++len]=nx;
next_equation(nx);
}
sort(z+1,z+len+1);
for(int i=1;i<len;i++)printf("%.4lf ",z[i]);
printf("%.4lf\n",z[len]);
cin>>n;
}
sjh0626s code;
}
题目 2:P1024 [NOIP 2001 提高组] 一元三次方程求解
这其实和上一道题一样,只是变成了求三次方程,一样,上模板!
代码:
#include<bits/stdc++.h>
#define sjh0626s return
#define code 0
#define ll long long
#define PII pair<int,int>
using namespace std;
int t=1;
double a,b,c,d;
template<class T>
class Newton{ //牛顿迭代法封装
public:
T a[100010];
T b[100010];
int n;
Newton(){}
Newton(initializer_list<T>lis){
n=lis.size()-1;
int i=n;
for(auto it=lis.begin();it!=lis.end();it++,i--)a[i]=*it;
}
Newton(vector<T>vec){
n=vec.size()-1;
int i=n;
for(auto it=vec.begin();it!=vec.end();it++,i--)a[i]=*it;
}
T f(T x){ //原函数
T res(0.0),px(1.0);
for(int i=0;i<=n;i++){res=res+a[i]*px,px=px*x;}
return res;
}
T df(T x){ //导数
T res(0.0),px(1.0);
for(int i=1;i<=n;i++)res=res+a[i]*i*px,px=px*x;
return res;
}
T solve(T x){ //迭代
while(fabs(f(x))>1e-12)x=x-f(x)/df(x);
return x;
}
void change(T z){ //因式分解求 f(x)=(x-z)g(x)
b[n]=0;
for(int i=n-1;i>=0;i--)b[i]=a[i+1]+b[i+1]*z;
for(int i=n;i>=0;i--)a[i]=b[i];
}
};
set<double>ans;
void solve(){
cin>>a>>b>>c>>d;
Newton<double> s({a,b,c,d}); //初始化系数
for(int i=1;i<=3;i++){
double z=s.solve(1);
ans.insert(z);
s.change(z);
}
for(auto z:ans){
printf("%.2lf ",z);
}
}
int main(){
// freopen(".in","r",stdin);
// freopen(".out","w",stdout);
// cin>>t;
while(t--){
solve();
}
sjh0626s code;
}
求复平面上根
题目:SP481 KMSL4B - Roots of polynomial
这时就又有热于提问的观众要问了:“主播主播,一般的我们会了,但是复数运算我们不会啊!”
欸,那我们先说复数吧!
我们要知道复数的概念,要先知道虚数单位 i,i 的定义是 i^2=-1。很好,可以讲复数了,复数有一个实部 a,还有一个虚部 b,那么它的值就是 z=a+bi。
先说加法,很简单啊,就是两边实部相加,虚部相加啊。那么值就是 z=(a+bi)+(c+di)=(a+c)+(b+d)i。
减法就不用了,和加法一样。
再说乘法吧,这也不算难,就只需要展开而已,z=(a+bi)(c+di)=ac+adi+bci-bd=(ac-bd)+(ad+bc)i。
最后再说除法吧,这咋办啊,我们要多项式除法啊,不用慌,一个待定系数,解决所有问题。
我们设 (q+pi)(c+di)=a+bi。
展开,得 (qc-pd)+(qd+pc)i=a+bi。
由此得 a=qc-pd,b=qd+pc。
再推,得 p=\frac{qc-a}{d}。
带入 b,得 b=qd+\frac{qc^2-ac}{d}=\frac{qd^2+qc^2-ac}{d}。
解一下 q:
\begin{aligned}
bd&=qd^2+qc^2-ac\\
bd+ac&=q(c^2+d^2)\\
\frac{bd+ac}{c^2+d^2}&=q
\end{aligned}
$$p=\frac{\frac{bcd+ac^2}{c^2+d^2}-a}{d}=\frac{\frac{bcd-ad^2}{c^2+d^2}}{d}=\frac{bc-ad}{c^2+d^2}$$
由此可得 $z=\frac{a+bi}{c+di}=\frac{ac+bd}{c^2+d^2}+\frac{bc-ad}{c^2+d^2}i$。
除法讲完了,可以做了。。。欸不对啊!题目说要复平面上 $\lvert z \rvert<1$。欸?这个不是绝对值吗?在复平面中绝对值的符号其实是**模**,对于一个复数 $z$,它的模为 $\sqrt{a^2+b^2}$,我们只需要求这个值是否 $<1$ 就对了。
我们就只用把模板改成虚数的就对了。
**代码**:
```cpp
#include<bits/stdc++.h>
#define sjh0626s return
#define code 0
#define ll long long
#define PII pair<int,int>
using namespace std;
int t=1;
class comp{
public:
double r,i;
comp(double a=0.0,double b=0.0){r=a,i=b;}
comp operator+(comp b){return comp(r+b.r,i+b.i);}
comp operator-(comp b){return comp(r-b.r,i-b.i);}
comp operator*(comp b){return comp(r*b.r-i*b.i,r*b.i+i*b.r);}//(ac-bd)+i(ad+bc)
comp operator/(comp b){return comp((r*b.r+i*b.i)/(b.r*b.r+b.i*b.i),(i*b.r-r*b.i)/(b.r*b.r+b.i*b.i));}
comp operator*(double b){return comp(r*b,i*b);}
comp operator/(double b){return comp(r/b,i/b);}
void operator=(double b){r=b,i=0;}
bool operator>(double b){return r>b||i>b;}
double abs(){return sqrt(r*r+i*i);}
};
comp fabs(comp a){
return comp(fabs(a.r),fabs(a.i));
}
template<class T>
class Newton{ //牛顿迭代法封装
public:
T a[100010];
T b[100010];
int n;
Newton(){}
Newton(initializer_list<T>lis){
n=lis.size()-1;
int i=n;
for(auto it=lis.begin();it!=lis.end();it++,i--)a[i]=*it;
}
Newton(vector<T>vec){
n=vec.size()-1;
int i=n;
for(auto it=vec.begin();it!=vec.end();it++,i--)a[i]=*it;
}
T f(T x){ //原函数
T res,px(1.0);
for(int i=0;i<=n;i++){res=res+a[i]*px,px=px*x;}
return res;
}
T df(T x){ //导数
T res,px(1.0);
for(int i=1;i<=n;i++)res=res+a[i]*i*px,px=px*x;
return res;
}
T solve(T x={1,1}){ //迭代
while(fabs(f(x))>1e-12)x=x-f(x)/df(x);
return x;
}
void change(T z){ //因式分解求 f(x)=(x-z)g(x)
b[n]=0;
for(int i=n-1;i>=0;i--)b[i]=a[i+1]+b[i+1]*z;
for(int i=n;i>=0;i--)a[i]=b[i];
}
};
int k;
comp z;
void solve(){
cin>>k;
Newton<comp> poly;
poly.n=k;
for(int i=0;i<=k;i++)cin>>poly.a[i].r;
for(int i=1;i<=k;i++){
z=poly.solve({1,1});
if(z.abs()>=1){
cout<<"0\n";
return;
}
poly.change(z);
}
cout<<"1\n";
}
int main(){
// freopen(".in","r",stdin);
// freopen(".out","w",stdout);
cin>>t;
while(t--){
solve();
}
sjh0626s code;
}
```
# 模板
因为目前不知道怎么处理除幂函数以外的函数,所以先这样吧,以后知道怎么做了再补吧。
```cpp
class comp{
public:
double r,i;
comp(double a=0.0,double b=0.0){r=a,i=b;}
comp operator+(comp b){return comp(r+b.r,i+b.i);}
comp operator-(comp b){return comp(r-b.r,i-b.i);}
comp operator*(comp b){return comp(r*b.r-i*b.i,r*b.i+i*b.r);}//(ac-bd)+i(ad+bc)
comp operator/(comp b){return comp((r*b.r+i*b.i)/(b.r*b.r+b.i*b.i),(i*b.r-r*b.i)/(b.r*b.r+b.i*b.i));}
comp operator*(double b){return comp(r*b,i*b);}
comp operator/(double b){return comp(r/b,i/b);}
void operator=(double b){r=b,i=0;}
bool operator>(double b){return r>b||i>b;}
double abs(){return sqrt(r*r+i*i);}
};
comp fabs(comp a){
return comp(fabs(a.r),fabs(a.i));
}
template<class T>
class Newton{ //牛顿迭代法封装
public:
T a[100010];
T b[100010];
int n;
Newton(){}
Newton(initializer_list<T>lis){
n=lis.size()-1;
int i=n;
for(auto it=lis.begin();it!=lis.end();it++,i--)a[i]=*it;
}
Newton(vector<T>vec){
n=vec.size()-1;
int i=n;
for(auto it=vec.begin();it!=vec.end();it++,i--)a[i]=*it;
}
T f(T x){ //原函数
T res,px(1.0);
for(int i=0;i<=n;i++){res=res+a[i]*px,px=px*x;}
return res;
}
T df(T x){ //导数
T res,px(1.0);
for(int i=1;i<=n;i++)res=res+a[i]*i*px,px=px*x;
return res;
}
T solve(T x){ //迭代
while(fabs(f(x))>1e-12)x=x-f(x)/df(x);
return x;
}
void change(T z){ //因式分解求 f(x)=(x-z)g(x)
b[n]=0;
for(int i=n-1;i>=0;i--)b[i]=a[i+1]+b[i+1]*z;
for(int i=n;i>=0;i--)a[i]=b[i];
}
};
```
这是一个模板类,可以使用不同类型(`double` 和 `comp`)。
这次就先到这里了,有疑问的可以在评论区提出。