SDOI 2008 烧水问题

SDOI 2008 烧水问题

洛谷传送门

题目描述

把总质量为1kg的水分装在n个杯子里,每杯水的质量均为(1/n)kg,初始温度均为0℃。现需要把每一杯水都烧开。我们可以对任意一杯水进行加热。把一杯水的温度升高t℃所需的能量为(4200t/n)J,其中,“J”是能量单位“焦耳”。如果一旦某杯水的温度达到100℃,那么这杯水的温度就不能再继续升高,此时我们认为这杯水已经被烧开。显然地,如果直接把水一杯一杯地烧开,所需的总能量为(4200100)J。

在烧水的过程中,我们随时可以在两杯温度不同的水之间进行热传递操作。热量只能从温度较高的那杯水传递到温度较低的那杯水。由于两杯水的质量相同,所以进行热传递操作之后,原来温度较高的那杯水所降低的温度总是等于原来温度较低的那杯水所升高的温度。

一旦两杯水的温度相同,热传递立刻停止。

为了把问题简化,我们假设:

1、没有进行加热或热传递操作时,水的温度不会变化。

2、加热时所花费的能量全部被水吸收,杯子不吸收能量。

3、热传递总是隔着杯子进行,n杯水永远不会互相混合。

4、热传递符合能量守恒,而且没有任何的热量损耗。

在这个问题里,只要求把每杯水都至少烧开一遍就可以了,而不要求最终每杯水的温度都是100℃。我们可以用如下操作把两杯水烧开:先把一杯水加热到100℃,花费能量(4200100/2)J,然后两杯水进行热传递,直到它们的温度都变成50℃为止,最后把原来没有加热到100℃的那杯水加热到100℃,花费能量(420050/2)J,此时两杯水都被烧开过了,当前温度一杯100℃,一杯50℃,花费的总能量为(420075)J,比直接烧开所需的(4200100)J少花费了25%的能量。

你的任务是设计一个最佳的操作方案使得n杯水都至少被烧开一遍所需的总能量最少。

输入格式

输入文件只有一个数n。

输出格式

输出n杯水都至少被烧开一遍所需的最少的总能量,单位为J,四舍五入到小数点后两位。


题解:

贪心,发现肯定不能逐一加热,而是一杯一杯热。那么就是先热第一杯,然后开始传递热量。传递热量的话,一定要都传递完毕,也就是第二杯传50,第三杯传25,一直到第n杯都有的传。

然后再热第二杯,这时就只需要加热50度了,同理,继续传递热量到n个杯子。

那么我们稍微推一下,假设第i杯还需要加热的热量为\(tot[i]\),那么有:

\[tot[i]=100-(\frac{\sum_{j=2}^{j=i-1}\frac{100}{2^j}+100}{2}) \]

非常兴奋,发现这个等比数列求和可以O(n)递推。但是最后就错了。因为这个递推在i特别大的时候会丢太多的精度。所以就挂了。

那么怎么办呢?到手的分没有了QAQ。

由于tot[i]的形式非常相似,我们看看可不可以在相邻两项直接递推tot[i]?经过手推样例,可以发现:

\[tot[i+1]/tot[i]=\frac{2i-1}{2i} \]

然后就可以无精度损失地递推了。

代码:

#include<cstdio>
using namespace std;
const int maxn=3e6+6;
double t[maxn];
int n;
int main()
{
	// freopen("water.in","r",stdin);
	// freopen("water.out","w",stdout);
	scanf("%d",&n);
	double tot=100.0;
	t[1]=100.0;
	for(int i=2;i<=n;i++)
	{
		t[i]=t[i-1]*(2*(i-1)-1)/(2*(i-1));
		tot+=t[i];
	}
	double ans=4200*tot/n;
	printf("%.2lf\n",ans);
	return 0;
}
posted @ 2020-12-02 14:23  Seaway-Fu  阅读(108)  评论(0编辑  收藏  举报