[MtOI2019]灵梦的计算器 解题报告

题目链接:https://www.luogu.org/problem/P5515

题目背景

注: 该背景部分改编自 disangan233 中考前买计算器的真实事件。

博丽 灵梦 (Hakurei Reimu) 在成功抢回八云 紫 (Yakumo Yukari) 用隙间偷走的香火钱后,她和依神 紫苑 (Yorigami Shion) 去香霖堂买东西啦!

灵梦想买一个计算器来计算神社的香火钱,但是因为香霖堂的东西太贵了,她选择使用河童重工网络 (Kawashiro Nitori's Network,KNN) 网购一个 Casio 计算器。

但出人意料的是,灵梦使用 KNN 买回来的 Casio 是个假货,最多只能显示整数部分(即向下取整)。

灵梦很苦恼,因为这个计算器可能会导致一些特别大的误差。所以灵梦想让拥有外界的式神(指电脑)的你帮她解决一个问题。

题目描述

灵梦得到了3个实数 n ,a ,b ( 4≤n≤5,5≤a,b≤10 ) ,她成功地计算了 n^a+n^b,得到了一个只显示整数部分的结果。

灵梦想知道,若存在一个实数 n′(n′≥0),使得 n'^a+n'^b 的结果在计算器上与 n^a+n^b 的结果显示出来完全一致时,n′ 的变化范围,即 n′ 的最大值与最小值之差。

如果你不知道如何计算 n^k,请使用cmath库的pow()函数,pow(n,k)的结果即为 n^k 的结果。


为了提高本题的难度,灵梦给你设置了 T 组询问。而为了在某种程度上减少你的输入和输出量,我们采用以下的代码来生成询问(代码来自河童重工):

namespace Mker
{
//  Powered By Kawashiro_Nitori
//  Made In Gensokyo, Nihon
	#define uint unsigned int
	uint sd;int op;
	inline void init() {scanf("%u %d", &sd, &op);}
	inline uint uint_rand()
	{
		sd ^= sd << 13;
		sd ^= sd >> 7;
		sd ^= sd << 11;
		return sd;
	}
	inline double get_n()
	{
		double x = (double) (uint_rand() % 100000) / 100000;
		return x + 4;
	}
	inline double get_k()
	{
		double x = (double) (uint_rand() % 100000) / 100000;
		return (x + 1) * 5;
	}
	inline void read(double &n,double &a, double &b)
	{
		n = get_n(); a = get_k();
		if (op) b = a;
		else b = get_k(); 
	}
}

在调用 Mker::init() 函数之后,你第 i 次调用 Mker::read(n,a,b) 函数后得到的便是第 i 次询问的 n_i, a_ib_i

为了减少你的输出量,令第 i 次询问的答案为 s_i,你只需要输出 \sum_{ i=1}^Ts_i​ 。如果你的答案与标准答案的绝对误差在 10^{-2} 以内,你的答案则被视为是正确答案。

本题数据的生成采用时间复杂度远远劣于普通算法的高 (da) 精 (bao) 度 (li) 算法来保证精度,本题数据保证单次询问的误差小于 10^{-10}所以本题的SPJ范围对于正解来说是完全足够的。


为了让你更好地做题,这里给出了关于 op 的说明:

  • 当 op=1 时,有 a=b,否则无特殊限定。

输入格式

输入共一行,包含 3 个正整数 T,seed,op,含义见题目描述。

输出格式

输出共一行,输出题目描述中要求输出的答案。

 

先看一眼题目。

题目大意是:给出n,a,b,求n_1-n_0,使得[n_1^a+n_1^b]=[n_0^a+n_0^b]=[n^a+n^b],且不存在x > n_1x<n_0,使得[x^a+x^b]=[n^a+n^b]成立。

换句话说,假设f(x)=x^a+x^bk=[n^a+n^b],那么\lim_{x\rightarrow n_1}f(x)=k+1,\lim_{x\rightarrow n_0}f(x)=k

Solution 1 二分

发现这个f(n)=n^a+n^b是个单调函数!所以直接二分分别求出n_0,n_1就可以了!

#include<cstdio>
#include<cmath>
using namespace std;
namespace Mker
{
//  Powered By Kawashiro_Nitori
//  Made In Gensokyo, Nihon
	#define uint unsigned int
	uint sd;int op;
	inline void init() {scanf("%u %d", &sd, &op);}
	inline uint uint_rand()
	{
		sd ^= sd << 13;
		sd ^= sd >> 7;
		sd ^= sd << 11;
		return sd;
	}
	inline double get_n()
	{
		double x = (double) (uint_rand() % 100000) / 100000;
		return x + 4;
	}
	inline double get_k()
	{
		double x = (double) (uint_rand() % 100000) / 100000;
		return (x + 1) * 5;
	}
	inline void read(double &n,double &a, double &b)
	{
		n = get_n(); a = get_k();
		if (op) b = a;
		else b = get_k(); 
	}
}
int main()
{
	double ans=0;
	int t;
	scanf("%d",&t);
	Mker::init();
	for (int i=1;i<=t;i++)
	{
		double n,a,b;
		Mker::read(n,a,b);
		int tot=floor(pow(n,a)+pow(n,b));
		double r=20;
		double l=n;
		while (l<r-0.000000001)
		{
			double mid=(l+r)/2;
			if (tot<floor(pow(mid,a)+pow(mid,b))) r=mid; else l=mid;
		}
		double m=l;
		l=0;
		r=n;
		while (l<r-0.000000001)
		{
			double mid=(l+r)/2;
			if (tot>floor(pow(mid,a)+pow(mid,b))) l=mid; else r=mid;
		}
		ans+=m-l;
	}
	printf("%0.10lf",ans);
	return 0;
}

然后发现只有65pts

Solution 2 不用迭代的牛顿迭代法

分析一下二分的时间复杂度,假设精度为bit,那么显然时间复杂度为O(Tlog10^{bit}),会超时。

考虑牛顿迭代,因为\lim_{x\rightarrow n_1}f(x)=k+1,\lim_{x\rightarrow n_0}f(x)=k,那么只要用牛顿迭代求出f(x)=k+1f(x)=k的解,分别代入n_1,n_0即可。

由于我们可以目力观测n_0,n_1必定分别在n左右很小的范围内,所以只要把估值设为n,进行一次迭代即可,而不需要多次迭代。

#include<cstdio>
#include<cmath>
using namespace std;
	int t;
	double n,a,b;
namespace Mker
{
//  Powered By Kawashiro_Nitori
//  Made In Gensokyo, Nihon
	#define uint unsigned int
	uint sd;int op;
	inline void init() {scanf("%u %d", &sd, &op);}
	inline uint uint_rand()
	{
		sd ^= sd << 13;
		sd ^= sd >> 7;
		sd ^= sd << 11;
		return sd;
	}
	inline double get_n()
	{
		double x = (double) (uint_rand() % 100000) / 100000;
		return x + 4;
	}
	inline double get_k()
	{
		double x = (double) (uint_rand() % 100000) / 100000;
		return (x + 1) * 5;
	}
	inline void read(double &n,double &a, double &b)
	{
		n = get_n(); a = get_k();
		if (op) b = a;
		else b = get_k(); 
	}
}
int main()
{
	scanf("%d",&t);
	Mker::init();
	double ans=0;
	for (int i=1;i<=t;i++)
	{
		Mker::read(n,a,b);
		int k=pow(n,a)+pow(n,b);
		double r=n;
		r-=(pow(r,a)+pow(r,b)-double(k+1))/(a*pow(r,a-1)+b*pow(r,b-1));
		ans+=r;
		r=n;
		r-=(pow(r,a)+pow(r,b)-double(k))/(a*pow(r,a-1)+b*pow(r,b-1));
		ans-=r;
	}
	printf("%.3lf",ans);
	return 0;
}

按理说,单次询问复杂度为O(1),那么用牛顿迭代总时间复杂度为O(T),应该是可以通过的。

但实际上,这只能得80pts,原因是pow函数比较慢,使得常数较大。

通过特殊的卡常技巧可以通过此题,当然也可以进一步推导来寻求更简单的方法。

Solution 3 用牛顿迭代推出结论

既然只用迭代一次,那么我们可以直接得到n_0,n_1的公式:

n_0=n-\frac{f(n)-k}{f'(n)}

n_1=n-\frac{f(n)-(k+1)}{f'(n)}

那么单次询问答案就为n_1-n_0=\frac{1}{f'(n)}=\frac{1}{an^{a-1}+bn^{b-1}}

把所有答案加起来即可,时间复杂度为O(T),可以得到全部分数。

#include<cstdio>
#include<cmath>
using namespace std;
	int t;
	double n,a,b;
namespace Mker
{
//  Powered By Kawashiro_Nitori
//  Made In Gensokyo, Nihon
	#define uint unsigned int
	uint sd;int op;
	inline void init() {scanf("%u %d", &sd, &op);}
	inline uint uint_rand()
	{
		sd ^= sd << 13;
		sd ^= sd >> 7;
		sd ^= sd << 11;
		return sd;
	}
	inline double get_n()
	{
		double x = (double) (uint_rand() % 100000) / 100000;
		return x + 4;
	}
	inline double get_k()
	{
		double x = (double) (uint_rand() % 100000) / 100000;
		return (x + 1) * 5;
	}
	inline void read(double &n,double &a, double &b)
	{
		n = get_n(); a = get_k();
		if (op) b = a;
		else b = get_k(); 
	}
}
int main()
{
	scanf("%d",&t);
	Mker::init();
	double ans=0;
	for (int i=1;i<=t;i++)
	{
		Mker::read(n,a,b);
		ans+=1/(a*pow(n,a-1)+b*pow(n,b-1)); 
	}
	printf("%.3lf",ans);
	return 0;
}

(你问我为什么贴了这么多代码?没错,上面就是我的心(ti)路(jiao)历程)

Solution 4 数学推导

这是在题解里看到的,非常有意思。

在Solution 2中,我们已经知道n_0,n_1n十分接近,因此我们可以把f(x)的图像画出来,把[n_0,n_1]段看成直线。

为什么可以看成直线呢?

如果你是高中生,应该在数学课上学定积分时学过“化曲为直”的思想,当时为了方便计算“曲边梯形”的面积,我们在很小的一个区间内把曲线看成直线从而转换为普通的梯形求其面积。

当然上述“化曲为直”的依据是区间无限小,在这里,我们的区间只能说是很小,但并不是无限小。

那么在本题中,“化曲为直”依据是什么呢?

精度。

题目的精度要求不高,因此可以粗略地把曲线看成直线。(当然即使这样,这种方法还是比较冒险,我个人不建议用)

那么我们可以发现在[n_0,n_1]段,构成了一个直角三角形,其中tan\alpha=f'(n).

那么由三角函数知识,在图中的直角三角形内,n_1-n_0=(f(n_1)-f(n_0))\times tan\alpha=(f(n_1)-f(n_0))\times f'(n)=\frac{1}{an^{a-1}+bn^{b-1}}

时间复杂度为O(T);实际上推出结论与Solution 4一致。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值