Gym100286C Clock

不想打题面,题面戳这里

被这题吓到了,感觉无从下手。最后还是看着题解和别人的代码加以改编最后写出了的。其实理解之后写出了也就是三四十行的样子了。
首先题目有个很重要的条件——转动某个针只会对周期比他长的钟产生影响,这似乎就给我们开了个口子。

我们假设第(i)根针有(D_{i-1})的刻度(第一根针 (60) 个刻度),然后我们会惊奇地发现,当第(i)根针运行一个周期后,第(i+1)根针恰好移动一个刻度。因为假设第(i)根针周期为(T),第(i+1)根针的周期就是(D_iT)。然后(i)这根指针转了一周,(i+1)恰好转了(frac{1}{D_i})周,也就是一个刻度。于是我们可以用(cost_i)记录下第(i)根针移动一个刻度所需要最小代价,dp方程很简单

[cost_i = min { cost_{i-1}*D_{i-1}, frac{2 pi L_i}{D_i} } ]

考虑到调针的代价其实只跟时间差的绝对值有关(顺逆时针等价),于是我们可以把问题等价成从0时刻将针调至时间差绝对值这个时刻所需要的最小代价。我们可以看看目标时刻指针所只在的位置,由于可能在两个位置中间,我们可以取下整。然后这个可以用进制转换一样的思想来处理,第(i)根指针用(val_i)记录。为什么取下整呢?后面你就会慢慢理解。

接下来,就是这道题目最核心的dp部分了。我们用(f_{i,0})表示将第(i)根指针转至(val_i)位置所需要的最小代价,(f_{i,1})表示将第(i)根指针转至(val_i+1)位置所需要的最小代价。由于周期长的不能影响周期短的,我们可以从后往前dp。我们如果已经把第(i)根针安放在([val_i,val_i+1))这段区间里面,那么移动周期比他短的指针时候也会在这个区间里面。因为(i-1)这根针我们最多转动一个周期,所以(i)这个针最多移动一个刻度,我们可以在dp方程中保证其合法性。然后根据针可以顺时针转也可以逆时针转,可以得出

[f[i][0] = min(f[i+1][1]+(D[i]-val[i])*cost[i],f[i+1][0]+val[i]*cost[i])\ f[i][1] = min(f[i+1][1]+(D[i]-val[i]-1)*cost[i],f[i+1][0]+(val[i]+1)*cost[i])]

这两个方程,最后答案就是(f[1][0])了。

#include<cstdio>
#include<cstdlib>
#include<cmath>
#include<iostream>
using namespace std;

typedef long long ll;
const int maxn = 55; const double pi = acos(-1.0);
int N,D[maxn],L[maxn],val[maxn]; ll T1,T2,T;
double cost[maxn],f[maxn][2];

int main()
{
	freopen("clock.in","r",stdin);
	freopen("clock.out","w",stdout);
	scanf("%d",&N);
	for (int i = 2;i <= N;++i) scanf("%d",D+i);
	for (int i = 1;i <= N;++i) scanf("%d",L+i);
	D[1] = 60; cost[1] = 2*pi*L[1]/D[1];
	for (int i = 2;i <= N;++i) cost[i] = min(cost[i-1]*D[i-1],2*pi*L[i]/D[i]);
	cin >> T1 >> T2; T = abs(T1-T2);
	for (int i = 1;i <= N&&T;T /= D[i++]) val[i] = T%D[i];
	for (int i = N;i;--i)
	{
		f[i][0] = min(f[i+1][1]+(D[i]-val[i])*cost[i],f[i+1][0]+val[i]*cost[i]);
		f[i][1] = min(f[i+1][1]+(D[i]-val[i]-1)*cost[i],f[i+1][0]+(val[i]+1)*cost[i]);
	}
	printf("%.10lf
",f[1][0]);
	fclose(stdin); fclose(stdout);
	return 0;
}
原文地址:https://www.cnblogs.com/mmlz/p/6360606.html