gmoj 6087. 【GDOI2019模拟2019.3.26】获取名额 题解

本文详细解析了一道高级数学题目,通过将乘积转换为求和,再利用泰勒公式将自然对数转换为多项式,进而解决了一个涉及指数与对数运算的复杂表达式。在实际编程实现时,还考虑了数值稳定性与精度控制的问题,展示了如何在算法中处理潜在的误差。
摘要由CSDN通过智能技术生成

题目

https://gmoj.net/senior/#main/show/6087

题解

发现
a n s = 1 − ∏ i = l r ( 1 − a i x ) ans=1-\prod_{i=l}^r \left(1-\frac{a_i}{x}\right) ans=1i=lr(1xai)
∏ \prod 比较难搞,就把它变成 ∑ \sum 吧!
a n s = 1 − e ∑ i = l r ln ⁡ ( 1 − a i x ) ans=1-e^{\sum_{i=l}^r \ln\left(1-\frac{a_i}{x}\right)} ans=1ei=lrln(1xai)
ln ⁡ \ln ln 比较难搞,就把它变成多项式函数吧!

为了方便处理,先弄出 ln ⁡ ( x + 1 ) \ln(x+1) ln(x+1) 的情况,再把 − a i x -\frac{a_i}{x} xai 代进去。

f ( x ) = ln ⁡ ( x + 1 ) f(x)=\ln(x+1) f(x)=ln(x+1)

泰勒公式 ,得
f ( x ) = ∑ i = 0 ∞ f ( i ) ( x 0 ) i ! ( x − x 0 ) i f(x)=\sum_{i=0}^{\infty} \frac{f^{(i)}(x_0)}{i!}(x-x_0)^i f(x)=i=0i!f(i)(x0)(xx0)i
其中 f ( i ) ( x ) f^{(i)}(x) f(i)(x) 表示 f ( x ) f(x) f(x) i i i 阶导数。

先求出 ( ln ⁡ ( x ) ) ′ (\ln(x))' (ln(x))

y = ln ⁡ x y=\ln x y=lnx ,那么
( ln ⁡ x ) ′ = 1 ( e y ) ′ = 1 e y = 1 x (\ln x)'=\frac{1}{(e^y)'}=\frac{1}{e^y}=\frac{1}{x} (lnx)=(ey)1=ey1=x1
所以
f ′ ( x ) = [ ln ⁡ ( x + 1 ) ] ′ ( x + 1 ) ′ = 1 x + 1 = ( x + 1 ) − 1 f ′ ′ ( x ) = − ( x + 1 ) − 2 f ′ ′ ′ ( x ) = 2 ( x + 1 ) − 3 ⋯ f ( n ) ( x ) = ( − 1 ) n + 1 ( n − 1 ) ! ( x + 1 ) − n \begin{aligned} f'(x)&=[\ln(x+1)]'(x+1)'=\frac{1}{x+1}=(x+1)^{-1}\\ f''(x)&=-(x+1)^{-2}\\ f'''(x)&=2(x+1)^{-3}\\ &\cdots\\ f^{(n)}(x)&=(-1)^{n+1}(n-1)!(x+1)^{-n} \end{aligned} f(x)f(x)f(x)f(n)(x)=[ln(x+1)](x+1)=x+11=(x+1)1=(x+1)2=2(x+1)3=(1)n+1(n1)!(x+1)n

那么
f ( x ) = ∑ i = 0 ∞ f ( i ) ( x 0 ) i ! ( x − x 0 ) i = ∑ i = 0 ∞ f ( i ) ( 0 ) i ! x i 令  x 0 = 0   = ∑ i = 1 ∞ ( − 1 ) i − 1 ( i − 1 ) ! i ! x i ln ⁡ 1 = 0 = ∑ i = 1 ∞ ( − 1 ) i − 1 x i i \begin{aligned} f(x)&=\sum_{i=0}^{\infty} \frac{f^{(i)}(x_0)}{i!}(x-x_0)^i\\ &=\sum_{i=0}^{\infty} \frac{f^{(i)}(0)}{i!}x^i &\text{令 $x_0=0$ }\\ &=\sum_{i=1}^{\infty} \frac{(-1)^{i-1}(i-1)!}{i!}x^i &\text{$\ln 1=0$}\\ &=\sum_{i=1}^{\infty} \frac{(-1)^{i-1}x^i}{i} \end{aligned} f(x)=i=0i!f(i)(x0)(xx0)i=i=0i!f(i)(0)xi=i=1i!(1)i1(i1)!xi=i=1i(1)i1xi x0=ln1=0
− a i x -\frac{a_i}{x} xai 代入,得
∑ i = l r ln ⁡ ( 1 − a i x ) = ∑ i = l r f ( a i x ) = − ∑ i = l r ∑ j = 1 ∞ a i j j ⋅ x j \begin{aligned} \sum_{i=l}^r \ln\left(1-\frac{a_i}{x}\right)&=\sum_{i=l}^r f\left(\frac{a_i}{x}\right)\\ &=-\sum_{i=l}^r \sum_{j=1}^{\infty} \frac{a_{i}^j}{j\cdot x^j} \end{aligned} i=lrln(1xai)=i=lrf(xai)=i=lrj=1jxjaij
在实际上做的时候,第二重循环只用枚举到 20 20 20 精度就足够了,因此用 前缀和 维护一个 ∑ i = l r ∑ j = 1 20 a i j j \sum_{i=l}^r \sum_{j=1}^{20} \frac{a_{i}^j}{j} i=lrj=120jaij 就可以了。

为了防止 a i j a_i^j aijlong double ,可以把每一个 a i a_i ai x x x 都除以一个 max ⁡ i = 1 n a i \max_{i=1}^n a_i maxi=1nai

还可以发现当 a i x \frac{a_i}{x} xai 比较大的时候,由于它离 x 0 x_0 x0 远,会有较大误差。这时只要暴力地把这些 a i x \frac{a_i}{x} xai 乘在一起而不把它们放到 ∑ \sum 中计算即可,如果这些 a i x \frac{a_i}{x} xai 较多答案就是 0 0 0 了。可以把阈值设置为 0.5 0.5 0.5


代码

#include<cstdio>
#include<cmath>
using namespace std;
#define fo(i,l,r) for(i=l;i<=r;++i)
typedef long double LF;
char p[15],buf[100005],*l=buf,*r=buf;
inline char gc()
{return l==r&&(r=(l=buf)+fread(buf,1,100005,stdin),l==r)?EOF:*l++;}
inline void read(int &k)
{
	char ch;while(ch=gc(),ch<'0'||ch>'9');k=ch-48;
	while(ch=gc(),ch>='0'&&ch<='9') k=k*10+ch-48;
}
#define N 600005
const int lim=20;
LF a[N],s[N][lim+1],x,ans1,ans2;
int A[N],f[N][20],highbit[N];
inline int mymax(int x,int y){return a[x]>a[y]?x:y;}
void solve(int l,int r)
{
	if(l>r) return;
	int mid=mymax(f[r-(1<<highbit[r-l+1])+1][highbit[r-l+1]],f[l][highbit[r-l+1]]);
	if(a[mid]/x>0.5)
	{
		ans2*=1-a[mid]/x;
		if(ans2<1e-7) return;
		solve(l,mid-1),solve(mid+1,r);
	}
	else
	{
		LF tmp=x;
		for(register int i=1;i<=lim;++i,tmp*=x)
			ans1-=(s[r][i]-s[l-1][i])/tmp;
	}
}
int main()
{
	freopen("orz.in","r",stdin);
	freopen("orz.out","w",stdout);
	register int i;
	int j,k,n,q,l,r,y,maxx=0;
	LF max,tmp;
	read(n),read(q);
	fo(i,1,n)
	{
		read(A[i]);
		if(A[i]>maxx) maxx=A[i];
	}
	max=maxx;
	fo(i,1,n) a[i]=A[i]/max;
	j=0;
	fo(i,1,n)
	{
		if(i==1<<j+1) ++j;
		highbit[i]=j;
	}
	fo(i,1,n) f[i][0]=i;
	fo(j,1,19)
	{
		k=n-(1<<j)+1;
		fo(i,1,k)
			f[i][j]=mymax(f[i][j-1],f[i+(1<<j-1)][j-1]);
	}
	fo(i,1,n)
	{
		tmp=a[i];
		fo(j,1,lim)
		{
			s[i][j]=s[i-1][j]+tmp/j,
			tmp*=a[i];
		}
	}
	while(q--)
	{
		read(l),read(r),read(y);
		x=y/max,ans1=0,ans2=1;
		solve(l,r);
		printf("%.7Lf\n",1-exp(ans1)*ans2);
	}
	return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值