BZOJ5020: [THUWC 2017]在美妙的数学王国中畅游
BZOJ5020: [THUWC 2017]在美妙的数学王国中畅游
其实题面好像有点不全,建议去洛谷:
P4546 [THUWC2017]在美妙的数学王国中畅游
这里还是$BZOJ$的题面。
Description
Input
Output
Sample Input
1 1 0
3 0.5 0.5
3 -0.5 0.7
appear 0 1
travel 0 1 0.3
appear 0 2
travel 1 2 0.5
disappear 0 1
appear 1 2
travel 1 2 0.5
Sample Output
1.67942554e+000
1.20000000e+000
题解Here!
数字和数学规律主宰着这个世界。。。
学好数理化,走遍天下都不怕。。。
其实原题面有一个提示,我们称它为——泰勒展开。
如下:(选自洛谷)
若函数$f(x)$的$n$阶导数在$[a,b]$区间内连续,则对$f(x)$在$x_0(x_0\in[a,b])$处使用$n$次拉格朗日中值定理可以得到带拉格朗日余项的泰勒展开式:
$$f(x)=f(x_0)+\frac{f'(x_0)(x-x_0)}{1!}+\frac{f''(x_0)(x-x_0)^2}{2!}+ \cdots +\frac{f^{(n-1)}(x_0)(x-x_0)^{n-1}}{(n-1)!}+\frac{f^{(n)}(\xi)(x-x_0)^n}{n!},x\in[a,b]$$
其中,当$x>x_0$时,$\xi\in[x_0,x]$;当$x<x_0$时,$\xi\in[x,x_0]$。
$f^{(n)}(x)$表示函数$f(x)$的$n$阶导数。
其实别看这么多乱七八糟的名词,关键就在于那个式子。
我们注意到,等号的左边是$f(x)$!
也就是说,我们可以不用搞其它的东西,只要把$f(x)$的$n$阶导数搞出来就好。
什么?你不知道导数?
回去学《数学 选修2-2》去吧。。。
这里给出此题要用到的导数计算:
基本公式:
$$f(x)=x\quad\Rightarrow\quad f'(x)=1$$
$$f(x)=\sin(x)\quad\Rightarrow\quad f'(x)=\cos(x)$$
$$f(x)=\cos(x)\quad\Rightarrow\quad f'(x)=-\sin(x)$$
$$f(x)=e^x\quad\Rightarrow\quad f'(x)=e^x$$
计算公式:
$$[f(x)+g(x)]'=f'(x)+g'(x)$$
$$[f(x)g(x)]'=f'(x)g(x)+f(x)g'(x)$$
$$\text{链式法则:}[f(g(x))]'=f'(g(x))\times g'(x)$$
于是我们可以愉快地推式子了!
以$f(x)=sin(x)$为例:
泰勒展开式中选取了一个$x_0$,我们为了方便,直接$x_0=0$。
设$f^n(x)$为$f(x)$的$n$阶导数。
那么:$$f(x)=f(0)+\frac{f^1(0)(x-0)}{1!}+\frac{f^2(0)(x-0)^2}{2!}+ \cdots +\frac{f^{n-1}(0)(x-0)^{n-1}}{(n-1)!}+\frac{f^{n}(\xi)(x-0)^n}{n!}$$
化简一下:$$f(x)=f(0)+\frac{f^1(0)}{1!}x+\frac{f^2(0)}{2!}x^2+ \cdots +\frac{f^{n-1}(0)}{(n-1)!}x^{n-1}+\frac{f^{n}(\xi)}{n!}x^n$$
但是这个$n$到底是多大呢?
其实,我们发现$n!$在$n$比较小时就是暴增函数。
也就是说,越往后的项对答案贡献越小,而精度也有$SPJ$,所以我们并不需要末尾那么多项。
这里我选取了$n=16$这个值,这是可以过的。
于是我们只要对每个点维护$16$项多项式就好。
但是又有人问了:题目中不是求$f(x)=sin(ax+b)$吗?
其实根据上面那个链式法则我们可以得出:$$f(x)=\sin(ax+b)\quad\Rightarrow\quad f'(x)=a\cos(ax+b)$$
同理对于另外两个式子有:
$$f(x)=ax+b\quad\Rightarrow\quad f'(x)=a$$
$$f(x)=e^{ax+b}\quad\Rightarrow\quad f'(x)=ae^{ax+b}$$
但是这只是一阶导数啊,高阶导数怎么办?
对于$f(x)=ax+b$,只有一阶导数$f'(x)=a$,高阶导数全部为$0$。
对于$f(x)=e^{ax+b}$,一阶导数$f'(x)=ae^{ax+b}$,然后每高一阶多乘一个$a$,如二阶导数为$f''(x)=a^2e^{ax+b}$。
对于$f(x)=\sin(ax+b)$,一阶导数$f'(x)=a\cos(ax+b)$,然后每高一阶多乘一个$a$,$\text{阶数}\mod 2==0$的导数为$\sin$,否则为$\cos$,并且$\text{阶数}\mod 4\leq1$的导数为正,否则为负。
由于精度会出锅,再加上每次计算$\sin(x),\cos(x),e^x$会比较慢,于是每次计算之前预处理一下即可。
至于阶乘。。。我一开始写了个逆元,发现模数不知道。。。
我这个大制杖好久才反应过来$16!=2.0922789888\times 10^{13}<10^{18}$。。。
所以直接开$double$预处理阶乘就好。
$LCT$应该不用多说,维护多项式的每一项单独的和。
答案就是多项式每一项之和。
至于那个什么科学计数法。。。大多数人直接$\%.8lf$。。。
其实我们可以直接用$C++$的自带版本——$\%.8e$!
自动将小数转成科学计数法!
还有啥细节那就看代码吧。。。
附代码:
#include<iostream> #include<algorithm> #include<cstdio> #include<cmath> #include<cstring> #define MAXN 100010 #define MAXM 20 using namespace std; int n,m; double fact[MAXM],val[MAXN][MAXM]; int top,stack[MAXN]; struct Link_Cut_Tree{ int f,flag,son[2]; double v[MAXM]; }a[MAXN]; inline int read(){ int date=0,w=1;char c=0; while(c<'0'||c>'9'){if(c=='-')w=-1;c=getchar();} while(c>='0'&&c<='9'){date=date*10+c-'0';c=getchar();} return date*w; } inline bool isroot(int rt){ return a[a[rt].f].son[0]!=rt&&a[a[rt].f].son[1]!=rt; } inline void pushup(int rt){ if(!rt)return; for(int i=0;i<=15;i++)a[rt].v[i]=a[a[rt].son[0]].v[i]+a[a[rt].son[1]].v[i]+val[rt][i]; } inline void pushdown(int rt){ if(!rt||!a[rt].flag)return; a[a[rt].son[0]].flag^=1;a[a[rt].son[1]].flag^=1;a[rt].flag^=1; swap(a[rt].son[0],a[rt].son[1]); } inline void turn(int rt){ int x=a[rt].f,y=a[x].f,k=a[x].son[0]==rt?1:0; if(!isroot(x)){ if(a[y].son[0]==x)a[y].son[0]=rt; else a[y].son[1]=rt; } a[rt].f=y;a[x].f=rt;a[a[rt].son[k]].f=x; a[x].son[k^1]=a[rt].son[k];a[rt].son[k]=x; pushup(x);pushup(rt); } void splay(int rt){ top=0; stack[++top]=rt; for(int i=rt;!isroot(i);i=a[i].f)stack[++top]=a[i].f; while(top)pushdown(stack[top--]); while(!isroot(rt)){ int x=a[rt].f,y=a[x].f; if(!isroot(x)){ if((a[y].son[0]==x)^(a[x].son[0]==rt))turn(rt); else turn(x); } turn(rt); } } void access(int rt){ for(int i=0;rt;i=rt,rt=a[rt].f){ splay(rt); a[rt].son[1]=i; pushup(rt); } } inline void makeroot(int rt){access(rt);splay(rt);a[rt].flag^=1;} int findroot(int rt){ access(rt);splay(rt); while(a[rt].son[0])rt=a[rt].son[0]; return rt; } inline void split(int x,int y){makeroot(x);access(y);splay(y);} inline void link(int x,int y){makeroot(x);a[x].f=y;} inline void cut(int x,int y){ split(x,y); if(a[y].son[0]==x&&a[x].f==y&&!a[x].son[1])a[x].f=a[y].son[0]=0; } void change(int x,int f,double a,double b){ memset(val[x],0,sizeof(val[x])); if(f==1){ double t=1,Sin=sin(b),Cos=cos(b); for(int i=0;i<=15;i++){ val[x][i]=((i%2)?Cos:Sin)*((i%4<=1)?1:-1)*t/fact[i]; t*=a; } } else if(f==2){ double t=1,Exp=exp(b); for(int i=0;i<=15;i++){ val[x][i]=Exp*t/fact[i]; t*=a; } } else{ val[x][0]=b; val[x][1]=a; } } double query(int x,int y,double k){ split(x,y); double s=0,t=1; for(int i=0;i<=15;i++){ s+=a[y].v[i]*t; t*=k; } return s; } void work(){ int u,v; double x,y,k; char ch[20]; while(m--){ scanf("%s",ch);u=read();v=read(); switch(ch[0]){ case 'a':{ u++;v++; link(u,v); break; } case 'd':{ u++;v++; cut(u,v); break; } case 'm':{ u++; access(u);splay(u); scanf("%lf%lf",&x,&y); change(u,v,x,y); pushup(u); break; } case 't':{ u++;v++;scanf("%lf",&k); if(findroot(u)!=findroot(v))printf("unreachable\n"); else printf("%.8e\n",query(u,v,k)); break; } } } } void init(){ int f; double x,y; char ch[5]; n=read();m=read();scanf("%s",ch); fact[0]=1; for(int i=1;i<=16;i++)fact[i]=fact[i-1]*i; for(int i=1;i<=n;i++){ f=read(); scanf("%lf%lf",&x,&y); change(i,f,x,y); } } int main(){ init(); work(); return 0; }