【bzoj5020】[THUWC 2017]在美妙的数学王国中畅游 泰勒展开+LCT
题目描述
学渣小R被大学的数学课程虐得生活不能自理,微积分的成绩曾是他在教室里上的课的最低分。然而他的某位陈姓室友却能轻松地在数学考试中得到满分。为了提升自己的数学课成绩,有一天晚上(在他睡觉的时候),他来到了数学王国。
数学王国中,每个人的智商可以用一个属于 [0,1]的实数表示。数学王国中有 n 个城市,编号从 0 到 n−1 ,这些城市由若干座魔法桥连接。每个城市的中心都有一个魔法球,每个魔法球中藏有一道数学题。每个人在做完这道数学题之后都会得到一个在 [0,1] 区间内的分数。一道题可以用一个从 [0,1] 映射到 [0,1]的函数 f(x) 表示。若一个人的智商为 x ,则他做完这道数学题之后会得到 f(x)分。函数 f有三种形式:
正弦函数 sin(ax+b) (a∈[0,1],b∈[0,π],a+b∈[0,π])
指数函数 e^(ax+b) (a∈[−1,1],b∈[−2,0],a+b∈[−2,0])
一次函数 ax+b (a∈[−1,1],b∈[0,1],a+b∈[0,1]
数学王国中的魔法桥会发生变化,有时会有一座魔法桥消失,有时会有一座魔法桥出现。但在任意时刻,只存在至多一条连接任意两个城市的简单路径(即所有城市形成一个森林)。在初始情况下,数学王国中不存在任何的魔法桥。
数学王国的国王拉格朗日很乐意传授小R数学知识,但前提是小R要先回答国王的问题。这些问题具有相同的形式,即一个智商为 x 的人从城市 u 旅行到城市 v(即经过 u 到 v 这条路径上的所有城市,包括 u和 v )且做了所有城市内的数学题后,他所有得分的总和是多少。
输入
第一行两个正整数 n,m 和一个字符串 type 。
表示数学王国中共有 n 座城市,发生了 m 个事件,该数据的类型为 type 。
typet 字符串是为了能让大家更方便地获得部分分,你可能不需要用到这个输入。
其具体含义在【数据范围与提示】中有解释。
接下来 n 行,第 i 行表示初始情况下编号为 i 的城市的魔法球中的函数。
一个魔法用一个整数 f表示函数的类型,两个实数 a,b 表示函数的参数,若
f=1,则函数为 f(x)=sin(ax+b)(a∈[0,1],b∈[0,π],a+b∈[0,π])
f=2,则函数为 f(x)=e^(ax+b)(a∈[−1,1],b∈[−2,0],a+b∈[−2,0])
f=3,则函数为 f(x)=ax+b(a∈[−1,1],b∈[0,1],a+b∈[0,1])
接下来 m行,每行描述一个事件,事件分为四类。
appear u v 表示数学王国中出现了一条连接 u 和 v 这两座城市的魔法桥 (0≤u,v<n,u≠v) ,保证连接前 u和 v 这两座城市不能互相到达。
disappear u v 表示数学王国中连接 u 和 v 这两座城市的魔法桥消失了,保证这座魔法桥是存在的。
magic c f a b 表示城市 c 的魔法球中的魔法变成了类型为 f ,参数为 a,b 的函数
travel u v x 表示询问一个智商为 x 的人从城市 u 旅行到城市 v
(即经过 u到 v 这条路径上的所有城市,包括 u 和 v )后,他得分的总和是多少。
若无法从 u 到达 v ,则输出一行一个字符串 unreachable。
1≤n≤100000,1≤m≤200000
输出
对于每个询问,输出一行实数,表示得分的总和。
样例输入
3 7 C1
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
样例输出
9.45520207e-001
1.67942554e+000
1.20000000e+000
题解
泰勒展开+LCT
如果对于每个子树直接维护函数的话很难维护。
注意到题目给出的函数都是收敛的,因此可以使用泰勒展开公式维护多项式。
本题用到的公式:
$e^x=\sum\limits_{i=0}^\infty \frac{x^i}{i!}\\ \sin x=\sum\limits_{i=0}^\infty (-1)^i\frac{x^{2i+1}}{(2i+1)!}$
要求$ax+b$对应的函数值直接把$ax+b$代入,用二项式定理展开即可。
注意到分母都是阶乘级别的,而$x$在$[0,1]$范围内,因此当$i$大到一定程度时可以忽略不计。因此可以只保留多项式的前几项,本题中保留16项即可保证精度。
剩下的就好办了,直接对LCT中Splay Tree的每个节点维护它的生成多项式和它子树生成多项式的和。查询时直接取出路径对应的生成多项式,把x代入即可出解。
时间复杂度$O(16n\log n+256n)=O(勉强能过)$,稍微有点卡常。。(不过像我这样自带超大常数的,跑了39s卡过了,应该也没什么特别卡的)
#include <cstdio> #include <cstring> #include <algorithm> #define N 100010 #define K 16 using namespace std; struct data { double w[K] , sum[K]; int fa , c[2] , rev; }a[N]; double p1[K] , p2[K] , fac[K] , choose[K][K]; char str[15]; inline void pushup(int x) { int i; for(i = 0 ; i < K ; i ++ ) a[x].sum[i] = a[a[x].c[0]].sum[i] + a[a[x].c[1]].sum[i] + a[x].w[i]; } inline void pushdown(int x) { if(a[x].rev) { int l = a[x].c[0] , r = a[x].c[1]; swap(a[l].c[0] , a[l].c[1]) , swap(a[r].c[0] , a[r].c[1]); a[l].rev ^= 1 , a[r].rev ^= 1 , a[x].rev = 0; } } inline bool isroot(int x) { return x != a[a[x].fa].c[0] && x != a[a[x].fa].c[1]; } void update(int x) { if(!isroot(x)) update(a[x].fa); pushdown(x); } inline void rotate(int x) { int y = a[x].fa , z = a[y].fa , l = (a[y].c[1] == x) , r = l ^ 1; if(!isroot(y)) a[z].c[a[z].c[1] == y] = x; a[x].fa = z , a[y].fa = x , a[a[x].c[r]].fa = y , a[y].c[l] = a[x].c[r] , a[x].c[r] = y; pushup(y) , pushup(x); } inline void splay(int x) { update(x); int y , z; while(!isroot(x)) { y = a[x].fa , z = a[y].fa; if(!isroot(y)) { if((a[y].c[0] == x) ^ (a[z].c[0] == y)) rotate(x); else rotate(y); } rotate(x); } } inline void access(int x) { int t = 0; while(x) splay(x) , a[x].c[1] = t , pushup(x) , t = x , x = a[x].fa; } inline int findroot(int x) { while(a[x].fa) x = a[x].fa; return x; } inline void makeroot(int x) { access(x) , splay(x) , swap(a[x].c[0] , a[x].c[1]) , a[x].rev ^= 1; } inline void link(int x , int y) { makeroot(x) , a[x].fa = y; } inline void split(int x , int y) { makeroot(x) , access(y) , splay(y); } inline void cut(int x , int y) { split(x , y) , a[x].fa = a[y].c[0] = 0 , pushup(y); } void work(int x , int f , double a1 , double a2) { memset(a[x].w , 0 , sizeof(a[x].w)); int i , j; for(i = 1 ; i < K ; i ++ ) p1[i] = p1[i - 1] * a1 , p2[i] = p2[i - 1] * a2; if(f == 1) { for(i = 1 ; i < K ; i += 4) for(j = 0 ; j <= i ; j ++ ) a[x].w[j] += p1[j] * p2[i - j] * choose[i][j] / fac[i]; for(i = 3 ; i < K ; i += 4) for(j = 0 ; j <= i ; j ++ ) a[x].w[j] -= p1[j] * p2[i - j] * choose[i][j] / fac[i]; } else if(f == 2) { for(i = 0 ; i < K ; i ++ ) for(j = 0 ; j <= i ; j ++ ) a[x].w[j] += p1[j] * p2[i - j] * choose[i][j] / fac[i]; } else a[x].w[1] = a1 , a[x].w[0] = a2; } inline void init() { int i , j; p1[0] = p2[0] = fac[0] = 1; for(i = 1 ; i < K ; i ++ ) fac[i] = fac[i - 1] * i; for(i = 0 ; i < K ; i ++ ) { choose[i][0] = 1; for(j = 1 ; j <= i ; j ++ ) choose[i][j] = choose[i - 1][j] + choose[i - 1][j - 1]; } } int main() { init(); int n , m , i , f , x , y; double a1 , a2 , ans; scanf("%d%d%*s" , &n , &m); for(i = 1 ; i <= n ; i ++ ) scanf("%d%lf%lf" , &f , &a1 , &a2) , work(i , f , a1 , a2) , pushup(i); while(m -- ) { scanf("%s" , str); switch(str[0]) { case 'a': scanf("%d%d" , &x , &y) , x ++ , y ++ , link(x , y); break; case 'd': scanf("%d%d" , &x , &y) , x ++ , y ++ , cut(x , y); break; case 'm': scanf("%d%d%lf%lf" , &x , &f , &a1 , &a2) , x ++ , splay(x) , work(x , f , a1 , a2) , pushup(x); break; default: { scanf("%d%d%lf" , &x , &y , &a1) , x ++ , y ++ ; if(findroot(x) != findroot(y)) puts("unreachable"); else { for(i = 1 ; i < K ; i ++ ) p1[i] = p1[i - 1] * a1; split(x , y) , ans = 0; for(i = 0 ; i < K ; i ++ ) ans += p1[i] * a[y].sum[i]; printf("%.8le\n" , ans); } } } } return 0; }