【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=sumlimits_{i=0}^infty frac{x^i}{i!}\ sin x=sumlimits_{i=0}^infty (-1)^ifrac{x^{2i+1}}{(2i+1)!}$

要求$ax+b$对应的函数值直接把$ax+b$代入,用二项式定理展开即可。

注意到分母都是阶乘级别的,而$x$在$[0,1]$范围内,因此当$i$大到一定程度时可以忽略不计。因此可以只保留多项式的前几项,本题中保留16项即可保证精度。

剩下的就好办了,直接对LCT中Splay Tree的每个节点维护它的生成多项式和它子树生成多项式的和。查询时直接取出路径对应的生成多项式,把x代入即可出解。

时间复杂度$O(16nlog 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
" , ans);
				}
			}
		}
	}
	return 0;
}